Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.github
*.md
docs/
tests/
templates/
workspace/
runtime-config/
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jobs:
just --list
(cd templates/enterprise && just --fmt --check && just --list)

- name: Test launcher behavior
run: python3 -m unittest discover -s tests -v

- name: Check Compose configuration
run: |
docker compose config --quiet
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/release-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ jobs:
- name: Build release image for scanning
env:
REGISTRY: workspace-test
TAG: latest
CACHE_FROM: "true"
CACHE_REGISTRY: ghcr.io/${{ github.repository_owner }}
CACHE_IMAGE: workspace-cache
Expand All @@ -131,7 +132,7 @@ jobs:
docker run --rm \
--entrypoint /bin/sh \
--volume /var/run/docker.sock:/var/run/docker.sock \
workspace-test/workspace:full -c '
workspace-test/workspace:full-latest -c '
set -eu
trivy image --download-db-only --no-progress
trivy image --download-java-db-only --no-progress
Expand All @@ -143,7 +144,7 @@ jobs:
--ignore-unfixed \
--scanners vuln \
--severity CRITICAL \
workspace-test/workspace:full
workspace-test/workspace:full-latest
'

- name: Publish code, platform, and full
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ secrets/
# Docker
.env

# Python test artifacts
__pycache__/

# OS
.DS_Store
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,37 @@ Containerized development workspace images and an enterprise overlay template.
just start # code
just start platform
just start full

# Use a published image without a local build
just pull platform
just start platform
```

`just start` builds only when the selected local image is missing. Use
`just up <flavor>` when you explicitly want to rebuild. Builds go through
`docker buildx bake`, not `docker compose up --build`.
`just up <flavor>` to rebuild. Both wait for the proxy and enabled browser
services to be healthy. Builds go through `docker buildx bake`.

Images use `<flavor>-<tag>`, such as `platform-latest`. Set `FLAVOR`, `TAG`, and
`REGISTRY` in `.env` to use the same selection for builds, pulls, and startup;
shell environment values take precedence. For a specific release, set
`TAG=1.3.0`, then run `just pull` and `just start`.

Use `just shell` for an interactive terminal, or `just exec <command> ...` to
run a command as `dev` in `/workspace`, including pipelines:

```bash
just exec git status
printf 'hello\n' | just exec cat
just health # proxy and enabled services
just status # container state and Docker health
```

Open:

- `http://localhost:8080` — workspace links
- `http://localhost:8080/code/` — code-server
- `http://localhost:8080/lab` — JupyterLab in `full`
- `http://localhost:8080/health` — proxy liveness
- `http://localhost:8080/status` — compact status

## Supported images

Expand Down Expand Up @@ -98,8 +116,8 @@ platform secret store.
## Requirements

- Docker with BuildKit/buildx
- Docker Compose v2
- [`just`](https://just.systems)
- Docker Compose v2 with `up --wait` support
- [`just`](https://just.systems) 1.54 or newer

## More docs

Expand Down
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

services:
workspace:
image: ${REGISTRY:-ghcr.io/jo-cube}/workspace:${FLAVOR:-code}
image: ${REGISTRY:-ghcr.io/jo-cube}/workspace:${FLAVOR:-code}-${TAG:-latest}
ports:
- "${WORKSPACE_BIND_ADDRESS:-127.0.0.1}:${WORKSPACE_PORT:-8080}:8080"
volumes:
Expand Down
5 changes: 0 additions & 5 deletions config/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@
respond "OK"
}

handle /status {
header Content-Type application/json
respond `{"status":"running"}`
}

@lab path /lab /lab/*
handle @lab {
reverse_proxy 127.0.0.1:8888
Expand Down
1 change: 0 additions & 1 deletion config/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ <h1>Workspace</h1>

<footer>
<a href="/health">Health</a>
<a href="/status">Status</a>
</footer>
</main>
</body>
Expand Down
6 changes: 3 additions & 3 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ target "code-core" {
target "code" {
dockerfile = "docker/runtime.Dockerfile"
context = "."
tags = ["${REGISTRY}/workspace:code-${TAG}", "${REGISTRY}/workspace:code"]
tags = ["${REGISTRY}/workspace:code-${TAG}"]
args = { BASE_IMAGE = "${REGISTRY}/workspace:code-core" }
contexts = { "${REGISTRY}/workspace:code-core" = "target:code-core" }
output = PUBLISH == "true" ? [] : ["type=docker"]
Expand All @@ -78,7 +78,7 @@ target "platform-core" {
target "platform" {
dockerfile = "docker/runtime.Dockerfile"
context = "."
tags = ["${REGISTRY}/workspace:platform-${TAG}", "${REGISTRY}/workspace:platform"]
tags = ["${REGISTRY}/workspace:platform-${TAG}"]
args = { BASE_IMAGE = "${REGISTRY}/workspace:platform-core" }
contexts = { "${REGISTRY}/workspace:platform-core" = "target:platform-core" }
output = PUBLISH == "true" ? [] : ["type=docker"]
Expand All @@ -96,7 +96,7 @@ target "full-core" {
target "full" {
dockerfile = "docker/runtime.Dockerfile"
context = "."
tags = ["${REGISTRY}/workspace:full-${TAG}", "${REGISTRY}/workspace:full", "${REGISTRY}/workspace:latest"]
tags = ["${REGISTRY}/workspace:full-${TAG}"]
args = { BASE_IMAGE = "${REGISTRY}/workspace:full-core" }
contexts = { "${REGISTRY}/workspace:full-core" = "target:full-core" }
output = PUBLISH == "true" ? [] : ["type=docker"]
Expand Down
3 changes: 2 additions & 1 deletion docker/full.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ esac >> /etc/arch-env
EOF

# JupyterLab
# Build the Rust kernel serially to keep peak compiler memory manageable.
USER dev
RUN --mount=type=cache,target=/cache/uv,sharing=locked,uid=1000,gid=1000 \
--mount=type=cache,target=/opt/rust/cargo/registry,sharing=locked,uid=1000,gid=1000 \
Expand All @@ -36,7 +37,7 @@ RUN --mount=type=cache,target=/cache/uv,sharing=locked,uid=1000,gid=1000 \
&& /opt/uv-tools/jupyterlab/bin/python -m bash_kernel.install --sys-prefix \
&& /opt/uv-tools/jupyterlab/bin/python -c 'import json,pathlib,sys; p=pathlib.Path(sys.prefix)/"share/jupyter/kernels/kotlin/kernel.json"; data=json.loads(p.read_text()); data["argv"][0]=sys.executable; data["metadata"]["jar_path_detect_command"][0]=sys.executable; p.write_text(json.dumps(data, indent=2)+"\n")' \
&& rustup component add rust-src \
&& cargo install --locked evcxr_jupyter \
&& cargo install --locked --jobs 1 evcxr_jupyter \
&& JUPYTER_PATH=/opt/uv-tools/jupyterlab/share/jupyter evcxr_jupyter --install

USER root
Expand Down
2 changes: 1 addition & 1 deletion docker/runtime.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ RUN chmod +x /etc/s6-overlay/s6-rc.d/caddy/run \
COPY config/cont-init.d/ /etc/cont-init.d/
RUN chmod +x /etc/cont-init.d/*

COPY scripts/ /scripts/
COPY scripts/doctor.sh scripts/healthcheck.sh /scripts/
RUN chmod +x /scripts/*.sh

COPY config/Caddyfile /etc/caddy/Caddyfile
Expand Down
8 changes: 6 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ image, so a failed browser application cannot leave the container healthy.
```text
/ static service links
/health static 200 response
/status {"status":"running"}
/code, /code/* code-server
/lab, /lab/* JupyterLab
```
Expand All @@ -71,4 +70,9 @@ proxy.
- `docker/*.Dockerfile` — readable internal layers and the final runtime overlay
- `docker-bake.hcl` — dependency graph for the three supported images
- `compose.yaml` — local launcher
- `justfile` — thin build and runtime commands
- `justfile` — thin build and runtime commands, including pull and command execution

Bake, Compose, and published releases use `<flavor>-<tag>` image names.
The launchers load `.env`, honor shell overrides, and wait for Docker health
before reporting successful startup. `just health` runs the same probe as
Docker; `/health` checks only Caddy liveness.
6 changes: 3 additions & 3 deletions docs/coder.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ resource "coder_agent" "main" {
}

resource "docker_image" "workspace" {
name = "ghcr.io/jo-cube/workspace:platform"
name = "ghcr.io/jo-cube/workspace:platform-latest"
keep_locally = true
}

Expand Down Expand Up @@ -146,7 +146,7 @@ coder port-forward <workspace-name> --tcp 8080:8080
Then access locally:
- `http://localhost:8080/code/` — code-server
- `http://localhost:8080/lab` — JupyterLab
- `http://localhost:8080/status` — workspace status
- `http://localhost:8080/health` — proxy liveness

## Single-port model

Expand All @@ -160,7 +160,7 @@ Choose the image flavor per workspace:

```hcl
resource "docker_image" "workspace" {
name = "ghcr.io/jo-cube/workspace:full"
name = "ghcr.io/jo-cube/workspace:full-latest"
}
```

Expand Down
26 changes: 26 additions & 0 deletions docs/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ Everything in `platform` plus:
- Trivy and Gitleaks
- hyperfine and sqlite3

## Image selection

Runtime image names are `ghcr.io/jo-cube/workspace:<flavor>-<tag>`, for example
`code-latest` or `full-1.3.0`. `TAG` defaults to `latest`. Local builds and
published releases use the same naming scheme.

To run a published image without a local build, put your selection in `.env`:

```dotenv
FLAVOR=platform
TAG=1.3.0
REGISTRY=ghcr.io/jo-cube
```

```bash
just pull
just start
```

`just pull` downloads the selected image without starting it. `just start`
uses the local image if present, otherwise builds it from this checkout.
`just up` always rebuilds. Use `just pull` again to refresh a moving tag.
An explicit flavor argument overrides `FLAVOR`; exported variables override
`.env`. Direct Bake invocations use exported variables, so use `just build`
when you want `.env` selection applied.

## Internal build layers

The Bake graph uses `base-core`, `code-core`, `polyglot-core`,
Expand Down
Loading
Loading