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
101 changes: 97 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,20 @@ jobs:
base:
runs-on: ubuntu-latest
timeout-minutes: 30
name: devcontainer-base${{ matrix.suffix }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- install_docker: true
suffix: ""
scope: base
- install_docker: false
suffix: "-nodocker"
scope: base-nodocker

steps:
- name: Checkout
Expand All @@ -40,6 +51,8 @@ jobs:
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/devcontainer-base
flavor: |
suffix=${{ matrix.suffix }},onlatest=true
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
Expand All @@ -53,13 +66,16 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=base
cache-to: type=gha,mode=max,scope=base
build-args: |
INSTALL_DOCKER=${{ matrix.install_docker }}
cache-from: type=gha,scope=${{ matrix.scope }}
cache-to: type=gha,mode=max,scope=${{ matrix.scope }}

# ── Phase 2: Build and push language images (parallel) ──────────────────────
variants:
needs: base
runs-on: ubuntu-latest
name: ${{ matrix.name }}${{ matrix.suffix }}
timeout-minutes: 30
permissions:
contents: read
Expand All @@ -71,19 +87,67 @@ jobs:
include:
- name: trixie-bun-nvm-uv-claude
scope: bun
base_scope: base
install_docker: true
suffix: ""
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-bun-nvm-uv-claude
scope: bun-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-php-nvm-uv-claude
scope: php
base_scope: base
install_docker: true
suffix: ""
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-php-nvm-uv-claude
scope: php-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-rust-nvm-uv-claude
scope: rust
base_scope: base
install_docker: true
suffix: ""
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-rust-nvm-uv-claude
scope: rust-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-vnc-nvm-uv-claude
scope: vnc
base_scope: base
install_docker: true
suffix: ""
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-vnc-nvm-uv-claude
scope: vnc-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-vnc-flutter-rust-nvm-uv-claude
scope: flutter
base_scope: base
install_docker: true
suffix: ""
needs_vnc: true
vnc_scope: vnc
build_contexts: trixie-vnc-nvm-uv-claude:latest=docker-image://localhost:5000/trixie-vnc-nvm-uv-claude:latest
- name: trixie-vnc-flutter-rust-nvm-uv-claude
scope: flutter-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
needs_vnc: true
vnc_scope: vnc-nodocker
build_contexts: trixie-vnc-nvm-uv-claude:latest=docker-image://localhost:5000/trixie-vnc-nvm-uv-claude:latest

services:
Expand All @@ -96,6 +160,29 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Free disk space
run: |
echo "Disk space before cleanup:"
df -h /
sudo rm -rf \
/usr/local/lib/android \
/usr/share/dotnet \
/opt/ghc \
/usr/local/.ghcup \
/usr/share/swift \
/usr/local/share/powershell \
/usr/local/share/chromium \
/usr/local/lib/node_modules \
/usr/local/share/boost \
/usr/lib/jvm \
/opt/hostedtoolcache
sudo swapoff -a || true
sudo rm -f /mnt/swapfile /swapfile || true
sudo docker image prune --all --force >/dev/null 2>&1 || true
sudo apt-get clean
echo "Disk space after cleanup:"
df -h /

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
Expand All @@ -118,7 +205,9 @@ jobs:
file: base/Dockerfile
push: true
tags: localhost:5000/devcontainer-base:latest
cache-from: type=gha,scope=base
build-args: |
INSTALL_DOCKER=${{ matrix.install_docker }}
cache-from: type=gha,scope=${{ matrix.base_scope }}

- name: Rebuild VNC from cache and push to local registry
if: matrix.needs_vnc
Expand All @@ -129,13 +218,17 @@ jobs:
push: true
tags: localhost:5000/trixie-vnc-nvm-uv-claude:latest
build-contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
cache-from: type=gha,scope=vnc
build-args: |
INSTALL_DOCKER=${{ matrix.install_docker }}
cache-from: type=gha,scope=${{ matrix.vnc_scope }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.name }}
flavor: |
suffix=${{ matrix.suffix }},onlatest=true
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
Expand Down
99 changes: 94 additions & 5 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,20 @@ jobs:
base:
runs-on: ubuntu-latest
timeout-minutes: 30
name: devcontainer-base${{ matrix.suffix }}
permissions:
contents: read
pull-requests: write
strategy:
fail-fast: false
matrix:
include:
- install_docker: true
suffix: ""
scope: base
- install_docker: false
suffix: "-nodocker"
scope: base-nodocker

steps:
- name: Checkout
Expand All @@ -32,7 +43,9 @@ jobs:
push: false
load: true
tags: devcontainer-base:latest
cache-from: type=gha,scope=base
build-args: |
INSTALL_DOCKER=${{ matrix.install_docker }}
cache-from: type=gha,scope=${{ matrix.scope }}

- name: Verify base tools
run: |
Expand All @@ -52,7 +65,7 @@ jobs:
ICON="❌"
fi
{
echo "### ${ICON} Tool Verification: base"
echo "### ${ICON} Tool Verification: base${{ matrix.suffix }}"
echo ""
echo "<details>"
echo "<summary>Click to expand verification output</summary>"
Expand All @@ -69,6 +82,7 @@ jobs:
variants:
needs: base
runs-on: ubuntu-latest
name: ${{ matrix.name }}${{ matrix.suffix }}
timeout-minutes: 30
permissions:
contents: read
Expand All @@ -80,19 +94,67 @@ jobs:
include:
- name: trixie-bun-nvm-uv-claude
scope: bun
base_scope: base
install_docker: true
suffix: ""
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-bun-nvm-uv-claude
scope: bun-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-php-nvm-uv-claude
scope: php
base_scope: base
install_docker: true
suffix: ""
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-php-nvm-uv-claude
scope: php-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-rust-nvm-uv-claude
scope: rust
base_scope: base
install_docker: true
suffix: ""
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-rust-nvm-uv-claude
scope: rust-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-vnc-nvm-uv-claude
scope: vnc
base_scope: base
install_docker: true
suffix: ""
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-vnc-nvm-uv-claude
scope: vnc-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
build_contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
- name: trixie-vnc-flutter-rust-nvm-uv-claude
scope: flutter
base_scope: base
install_docker: true
suffix: ""
needs_vnc: true
vnc_scope: vnc
build_contexts: trixie-vnc-nvm-uv-claude:latest=docker-image://localhost:5000/trixie-vnc-nvm-uv-claude:latest
- name: trixie-vnc-flutter-rust-nvm-uv-claude
scope: flutter-nodocker
base_scope: base-nodocker
install_docker: false
suffix: "-nodocker"
needs_vnc: true
vnc_scope: vnc-nodocker
build_contexts: trixie-vnc-nvm-uv-claude:latest=docker-image://localhost:5000/trixie-vnc-nvm-uv-claude:latest

services:
Expand All @@ -105,6 +167,29 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Free disk space
run: |
echo "Disk space before cleanup:"
df -h /
sudo rm -rf \
/usr/local/lib/android \
/usr/share/dotnet \
/opt/ghc \
/usr/local/.ghcup \
/usr/share/swift \
/usr/local/share/powershell \
/usr/local/share/chromium \
/usr/local/lib/node_modules \
/usr/local/share/boost \
/usr/lib/jvm \
/opt/hostedtoolcache
sudo swapoff -a || true
sudo rm -f /mnt/swapfile /swapfile || true
sudo docker image prune --all --force >/dev/null 2>&1 || true
sudo apt-get clean
echo "Disk space after cleanup:"
df -h /

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
Expand All @@ -120,7 +205,9 @@ jobs:
file: base/Dockerfile
push: true
tags: localhost:5000/devcontainer-base:latest
cache-from: type=gha,scope=base
build-args: |
INSTALL_DOCKER=${{ matrix.install_docker }}
cache-from: type=gha,scope=${{ matrix.base_scope }}

- name: Rebuild VNC from cache and push to local registry
if: matrix.needs_vnc
Expand All @@ -131,7 +218,9 @@ jobs:
push: true
tags: localhost:5000/trixie-vnc-nvm-uv-claude:latest
build-contexts: devcontainer-base:latest=docker-image://localhost:5000/devcontainer-base:latest
cache-from: type=gha,scope=vnc
build-args: |
INSTALL_DOCKER=${{ matrix.install_docker }}
cache-from: type=gha,scope=${{ matrix.vnc_scope }}

- name: Build ${{ matrix.name }}
uses: docker/build-push-action@v6
Expand All @@ -156,7 +245,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
VARIANT="${{ matrix.name }}"
VARIANT="${{ matrix.name }}${{ matrix.suffix }}"
if grep -q "ALL_CHECKS_PASSED" /tmp/verify-output.txt 2>/dev/null; then
ICON="✅"
else
Expand Down
2 changes: 1 addition & 1 deletion .planning/research/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Features that set the product apart. Not required, but valuable.
| Full Android Studio IDE via VNC | Most Flutter Docker images omit the IDE; having it lets developers use the full GUI (layout inspector, profiler, device manager) over VNC | HIGH | ~1 GB download. Silent install from tarball. Launchable via VNC. Most competitors only provide cmdline-tools. Adds significant image size but huge DX value. |
| Pre-created AVD device profile | Zero-config emulator start -- `emulator -avd dev_device` works immediately | LOW | `avdmanager create avd -n dev_device -k "system-images;android-35;google_apis;x86_64" -d pixel_7` during build. Saves developers 5-10 min of first-time setup. |
| Flutter + Rust FFI ready (cargo + Dart FFI) | Combined stack enables flutter_rust_bridge / rinf workflows out of the box; rare in DevContainer images | LOW | Both Rust and Flutter installed. No extra tooling needed beyond what's already table stakes. Documenting the capability in CLAUDE.md is the differentiator. |
| Claude Code + MCP servers pre-configured | AI-assisted development with semantic code analysis, context7 docs, memory -- unique to this project | LOW | Inherited from base image. Already configured with serena, context7, memory MCPs. No competitors have this. |
| Claude Code + MCP servers pre-configured | AI-assisted development with semantic code analysis, memory -- unique to this project | LOW | Inherited from base image. Already configured with memory MCPs. No competitors have this. |
| Chromium for Flutter web development | Enables `flutter run -d chrome` and `flutter test --platform chrome` inside the container | MEDIUM | `apt-get install chromium`. Set `CHROME_EXECUTABLE=/usr/bin/chromium`. Enables web target alongside Android without leaving the container. |
| Configurable Android API level via build arg | Different projects target different Android versions; build arg makes image flexible | LOW | `ARG ANDROID_API=35` controlling which platform/system-image/build-tools to install. Avoids maintaining multiple images. |
| Gradle + pub cache volume mount guidance | Dramatically faster rebuilds; avoids re-downloading 500+ MB of dependencies per project | LOW | Not baked into image, but documented in devcontainer.json examples. Mount `~/.gradle` and `~/.pub-cache` as named volumes. |
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ This is a project containing pre-configured Dockerfiles for building and hosting

## CI/CD notes

- Both workflows use a two-job structure: `base` builds the base image with GHA cache, then `variants` runs 5 parallel matrix jobs
- Both workflows use a two-job structure: `base` builds the base image with GHA cache, then `variants` runs the variant matrix jobs. Every image is built twice (Docker + no-docker) via the `INSTALL_DOCKER` build-arg: `base` runs 2 matrix jobs and `variants` runs 10 (5 × {docker, no-docker}); no-docker builds get a `-nodocker` tag suffix
- Variant jobs use a `registry:2` service container + `build-contexts` (via `matrix.build_contexts`) to remap FROM images to the local registry — no Dockerfile changes needed
- The flutter variant has a three-tier chain (base -> VNC -> flutter) with a conditional VNC rebuild step (`if: matrix.needs_vnc`)
- `build.yml` writes GHA cache (`cache-to`); `check.yml` only reads it (`cache-from`)
- GHA cache scopes: `base`, `bun`, `php`, `rust`, `vnc`, `flutter`
- GHA cache scopes: `base`, `bun`, `php`, `rust`, `vnc`, `flutter` (plus `base-nodocker`, `bun-nodocker`, `php-nodocker`, `rust-nodocker`, `vnc-nodocker`, `flutter-nodocker` for the no-docker builds)

## Testing and validation

Expand Down
Loading
Loading