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
11 changes: 7 additions & 4 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Project Overview

Dockerfiles for custom devcontainer images on GitHub Container Registry (ghcr.io). Each image provides a VS Code Dev Container for a specific development environment.
Dockerfiles for custom devcontainer images on GitHub Container Registry (ghcr.io). Each image provides a development container for a specific environment. Published under `ghcr.io/gatezh/devcontainers/`.

## Platform Constraints

Expand All @@ -12,9 +12,12 @@ Dockerfiles for custom devcontainer images on GitHub Container Registry (ghcr.io

## Naming Conventions

### Image names
- Devcontainer: `devcontainer-{tool}` or `devcontainer-{tool}-{secondary}`
- Standalone: `{base}-{variant}` (e.g., `ralphex-fe`)
### Directory names
- Named after the primary tool(s): `{tool}` or `{tool}-{secondary}` (e.g., `bun`, `hugo-bun`, `claude-code`)
- No `devcontainer-` prefix — the repo name `devcontainers` provides that context

### Image paths
- All images: `ghcr.io/gatezh/devcontainers/{directory-name}` (e.g., `ghcr.io/gatezh/devcontainers/bun`)

### Version ARGs in Dockerfile
- Place at top of file: `ARG {TOOL}_VERSION={version}`
Expand Down
4 changes: 2 additions & 2 deletions .claude/rules/dockerfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ paths:
All Dockerfiles must end with a single combined `LABEL` instruction:

```dockerfile
LABEL org.opencontainers.image.source="https://github.com/{owner}/devcontainer-images" \
LABEL org.opencontainers.image.source="https://github.com/{owner}/devcontainers" \
org.opencontainers.image.description="{Brief description}" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="{image-name}" \
org.opencontainers.image.url="https://github.com/{owner}/devcontainer-images"
org.opencontainers.image.url="https://github.com/{owner}/devcontainers"
```

## Base Images
Expand Down
14 changes: 7 additions & 7 deletions .claude/rules/new-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

## Devcontainer Image

1. Create `devcontainer-{name}/.devcontainer/Dockerfile`
2. Create `devcontainer-{name}/.devcontainer/devcontainer.json`
3. Create `devcontainer-{name}/README.md`
4. Create `.github/workflows/build-devcontainer-{name}.yml`
1. Create `{name}/.devcontainer/Dockerfile`
2. Create `{name}/.devcontainer/devcontainer.json`
3. Create `{name}/README.md`
4. Create `.github/workflows/build-{name}.yml`
5. Update root `README.md` with new image entry

## Standalone Docker Image

1. Create `{image-name}/Dockerfile` (no `.devcontainer/` subdirectory)
2. Create `{image-name}/README.md`
3. Create `.github/workflows/build-{image-name}.yml`
1. Create `{name}/Dockerfile` (no `.devcontainer/` subdirectory)
2. Create `{name}/README.md`
3. Create `.github/workflows/build-{name}.yml`
4. Update root `README.md` with new image entry
8 changes: 4 additions & 4 deletions .claude/rules/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ on:
push:
branches: [master]
paths:
- '{image-name}/.devcontainer/Dockerfile'
- '{image-name}/.devcontainer/*.sh'
- '{name}/.devcontainer/Dockerfile'
- '{name}/.devcontainer/*.sh'
workflow_dispatch:
```

Expand All @@ -43,8 +43,8 @@ on:
push:
branches: [master]
paths:
- '{image-name}/Dockerfile'
- '{image-name}/files/**'
- '{name}/Dockerfile'
- '{name}/files/**'
workflow_dispatch:
```

Expand Down
50 changes: 39 additions & 11 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ═══════════════════════════════════════════════════════════════════════════════
# Devcontainer for the devcontainer-images repo — two build targets:
# Devcontainer for the devcontainers repo — two build targets:
# default — full dev environment with passwordless sudo
# sandbox — network-restricted environment with firewall packages
#
Expand All @@ -9,8 +9,8 @@
# - npm-global directory with proper permissions
#
# Build:
# docker build --target default -t devcontainer-images:default .
# docker build --target sandbox -t devcontainer-images:sandbox .
# docker build --target default -t devcontainers:default .
# docker build --target sandbox -t devcontainers:sandbox .
# ═══════════════════════════════════════════════════════════════════════════════

# ═════════════════════════════════════════════════════════════════════════════
Expand All @@ -32,6 +32,28 @@ RUN set -eux; \
"https://github.com/rtk-ai/rtk/releases/download/v${RTK_VERSION}/rtk-${RTK_TARGET}.tar.gz"; \
tar -xzf /tmp/rtk.tar.gz -C /usr/local/bin rtk

# ── hadolint (Dockerfile linter) ────────────────────────────────────────
FROM alpine:3.21 AS hadolint-download
RUN apk add --no-cache curl jq
RUN set -eux; \
ARCH="$(uname -m)"; \
HADOLINT_VERSION=$(curl -fsSL https://api.github.com/repos/hadolint/hadolint/releases/latest \
| jq -r '.tag_name'); \
curl -fsSL -o /usr/local/bin/hadolint \
"https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-Linux-${ARCH}"; \
chmod +x /usr/local/bin/hadolint

# ── actionlint (GitHub Actions workflow linter) ────────────────────────
FROM alpine:3.21 AS actionlint-download
RUN apk add --no-cache curl jq
RUN set -eux; \
ARCH="$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')"; \
ACTIONLINT_VERSION=$(curl -fsSL https://api.github.com/repos/rhysd/actionlint/releases/latest \
| jq -r '.tag_name' | sed 's/^v//'); \
curl -fsSL -o /tmp/actionlint.tar.gz \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_${ARCH}.tar.gz"; \
tar -xzf /tmp/actionlint.tar.gz -C /usr/local/bin actionlint

# ── ralphex (autonomous plan execution) ──────────────────────────────────
FROM alpine:3.21 AS ralphex-download
RUN apk add --no-cache curl jq
Expand Down Expand Up @@ -117,6 +139,12 @@ RUN mkdir -p /home/node/.config/fish \
COPY --from=rtk-download /usr/local/bin/rtk /usr/local/bin/rtk
COPY --from=ralphex-download /usr/local/bin/ralphex /usr/local/bin/ralphex

# ── CI linting tools ────────────────────────────────────────────────────────
# hadolint (Dockerfile linter) and actionlint (GitHub Actions workflow linter).
# Enables running the same checks locally that CI runs on PRs.
COPY --from=hadolint-download /usr/local/bin/hadolint /usr/local/bin/hadolint
COPY --from=actionlint-download /usr/local/bin/actionlint /usr/local/bin/actionlint

# ── Claude Code CLI ──────────────────────────────────────────────────────────
# npm install (not native installer) to avoid rate-limiting in parallel builds.
# See: claude-code/.devcontainer/Dockerfile for rationale.
Expand All @@ -136,11 +164,11 @@ RUN echo "node ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/node-nopasswd \
&& chmod 0440 /etc/sudoers.d/node-nopasswd
USER node

LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainer-images" \
org.opencontainers.image.description="Devcontainer for the devcontainer-images repo — full dev environment" \
LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainers" \
org.opencontainers.image.description="Devcontainer for the devcontainers repo — full dev environment" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="devcontainer-images" \
org.opencontainers.image.url="https://github.com/gatezh/devcontainer-images"
org.opencontainers.image.title="devcontainers" \
org.opencontainers.image.url="https://github.com/gatezh/devcontainers"

CMD ["sleep", "infinity"]

Expand All @@ -165,10 +193,10 @@ RUN echo "node ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/node-nopasswd \
&& chmod 0440 /etc/sudoers.d/node-nopasswd
USER node

LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainer-images" \
org.opencontainers.image.description="Devcontainer for the devcontainer-images repo — network-restricted sandbox" \
LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainers" \
org.opencontainers.image.description="Devcontainer for the devcontainers repo — network-restricted sandbox" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="devcontainer-images-sandbox" \
org.opencontainers.image.url="https://github.com/gatezh/devcontainer-images"
org.opencontainers.image.title="devcontainers-sandbox" \
org.opencontainers.image.url="https://github.com/gatezh/devcontainers"

CMD ["sleep", "infinity"]
4 changes: 2 additions & 2 deletions .devcontainer/claude-sandbox/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@
}
},
"mounts": [
"source=devcontainer-images-sandbox-config-${devcontainerId},target=/home/node/.claude,type=volume",
"source=devcontainer-images-sandbox-fish-${devcontainerId},target=/home/node/.local/share/fish,type=volume",
"source=devcontainers-sandbox-config-${devcontainerId},target=/home/node/.claude,type=volume",
"source=devcontainers-sandbox-fish-${devcontainerId},target=/home/node/.local/share/fish,type=volume",
// Mount firewall script into the expected path
"source=${localWorkspaceFolder}/.devcontainer/claude-sandbox/init-firewall.sh,target=/usr/local/bin/init-firewall.sh,type=bind"
],
Expand Down
4 changes: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@
},
"mounts": [
// Persist Claude Code configuration between container rebuilds
"source=devcontainer-images-claude-config-${devcontainerId},target=/home/node/.claude,type=volume",
"source=devcontainers-claude-config-${devcontainerId},target=/home/node/.claude,type=volume",
// Persist fish shell history between container rebuilds
"source=devcontainer-images-fish-data-${devcontainerId},target=/home/node/.local/share/fish,type=volume"
"source=devcontainers-fish-data-${devcontainerId},target=/home/node/.local/share/fish,type=volume"
],
"containerEnv": {
"TZ": "${localEnv:TZ:America/Edmonton}",
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/init-plugins.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# Initialize Claude Code plugins for the devcontainer-images repo
# Initialize Claude Code plugins for the devcontainers repo
# This script is idempotent - safe to run multiple times

set -euo pipefail
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: Build devcontainer-bun
name: Build bun

on:
push:
branches:
- master
paths:
- 'devcontainer-bun/.devcontainer/Dockerfile'
- 'bun/.devcontainer/Dockerfile'
workflow_dispatch:

concurrency:
group: build-devcontainer-bun-${{ github.ref }}
group: build-bun-${{ github.ref }}
cancel-in-progress: true

jobs:
Expand All @@ -19,12 +19,12 @@ jobs:
version-tag: ${{ steps.versions.outputs.version-tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Extract versions from Dockerfile
id: versions
run: |
DOCKERFILE="devcontainer-bun/.devcontainer/Dockerfile"
DOCKERFILE="bun/.devcontainer/Dockerfile"
BUN_VERSION=$(grep '^ARG BUN_VERSION=' "$DOCKERFILE" | cut -d'=' -f2)
echo "version-tag=bun${BUN_VERSION}-alpine" >> "$GITHUB_OUTPUT"
echo "Bun: $BUN_VERSION"
Expand All @@ -36,9 +36,9 @@ jobs:
packages: write
uses: ./.github/workflows/reusable-docker-build.yml
with:
image-name: devcontainer-bun
context: devcontainer-bun/.devcontainer
dockerfile: devcontainer-bun/.devcontainer/Dockerfile
image-name: devcontainers/bun
context: bun/.devcontainer
dockerfile: bun/.devcontainer/Dockerfile
version-tag: ${{ needs.prepare.outputs.version-tag }}
verify-command: 'bun --version'
secrets: inherit
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: Build devcontainer-claude-bun
name: Build claude-bun

on:
push:
branches:
- master
paths:
- 'devcontainer-claude-bun/.devcontainer/Dockerfile'
- 'devcontainer-claude-bun/.devcontainer/init-firewall.sh'
- 'claude-bun/.devcontainer/Dockerfile'
- 'claude-bun/.devcontainer/init-firewall.sh'
workflow_dispatch:

concurrency:
group: build-devcontainer-claude-bun-${{ github.ref }}
group: build-claude-bun-${{ github.ref }}
cancel-in-progress: true

jobs:
Expand All @@ -20,12 +20,12 @@ jobs:
version-tag: ${{ steps.versions.outputs.version-tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Extract versions from Dockerfile
id: versions
run: |
DOCKERFILE="devcontainer-claude-bun/.devcontainer/Dockerfile"
DOCKERFILE="claude-bun/.devcontainer/Dockerfile"
BUN_VERSION=$(grep '^ARG BUN_VERSION=' "$DOCKERFILE" | cut -d'=' -f2)
echo "version-tag=bun${BUN_VERSION}-slim" >> "$GITHUB_OUTPUT"
echo "Bun: $BUN_VERSION"
Expand All @@ -37,9 +37,9 @@ jobs:
packages: write
uses: ./.github/workflows/reusable-docker-build.yml
with:
image-name: devcontainer-claude-bun
context: devcontainer-claude-bun/.devcontainer
dockerfile: devcontainer-claude-bun/.devcontainer/Dockerfile
image-name: devcontainers/claude-bun
context: claude-bun/.devcontainer
dockerfile: claude-bun/.devcontainer/Dockerfile
version-tag: ${{ needs.prepare.outputs.version-tag }}
verify-command: 'bun --version'
secrets: inherit
8 changes: 4 additions & 4 deletions .github/workflows/build-claude-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
context: claude-code/.devcontainer

platforms: linux/amd64,linux/arm64
meta-images: ghcr.io/gatezh/devcontainer-images/claude-code
meta-images: ghcr.io/gatezh/devcontainers/claude-code
meta-tags: |
type=raw,value=latest
type=sha,prefix=
Expand All @@ -56,7 +56,7 @@ jobs:
context: claude-code/.devcontainer

platforms: linux/amd64,linux/arm64
meta-images: ghcr.io/gatezh/devcontainer-images/claude-code-sandbox
meta-images: ghcr.io/gatezh/devcontainers/claude-code-sandbox
meta-tags: |
type=raw,value=latest
type=sha,prefix=
Expand Down Expand Up @@ -101,5 +101,5 @@ jobs:

- name: Verify image
run: |
docker pull ghcr.io/gatezh/devcontainer-images/${{ matrix.image-suffix }}:latest
docker run --rm ghcr.io/gatezh/devcontainer-images/${{ matrix.image-suffix }}:latest bash -c "${{ matrix.verify-command }}"
docker pull ghcr.io/gatezh/devcontainers/${{ matrix.image-suffix }}:latest
docker run --rm ghcr.io/gatezh/devcontainers/${{ matrix.image-suffix }}:latest bash -c "${{ matrix.verify-command }}"
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: Build devcontainer-hugo-bun-node
name: Build hugo-bun-node

on:
push:
branches:
- master
paths:
- 'devcontainer-hugo-bun-node/.devcontainer/Dockerfile'
- 'hugo-bun-node/.devcontainer/Dockerfile'
workflow_dispatch:

concurrency:
group: build-devcontainer-hugo-bun-node-${{ github.ref }}
group: build-hugo-bun-node-${{ github.ref }}
cancel-in-progress: true

jobs:
Expand All @@ -19,12 +19,12 @@ jobs:
version-tag: ${{ steps.versions.outputs.version-tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Extract versions from Dockerfile
id: versions
run: |
DOCKERFILE="devcontainer-hugo-bun-node/.devcontainer/Dockerfile"
DOCKERFILE="hugo-bun-node/.devcontainer/Dockerfile"
HUGO_VERSION=$(grep '^ARG HUGO_VERSION=' "$DOCKERFILE" | cut -d'=' -f2)
BUN_VERSION=$(grep '^ARG BUN_VERSION=' "$DOCKERFILE" | cut -d'=' -f2)
NODE_VERSION=$(grep '^ARG NODE_VERSION=' "$DOCKERFILE" | cut -d'=' -f2)
Expand All @@ -38,9 +38,9 @@ jobs:
packages: write
uses: ./.github/workflows/reusable-docker-build.yml
with:
image-name: devcontainer-hugo-bun-node
context: devcontainer-hugo-bun-node/.devcontainer
dockerfile: devcontainer-hugo-bun-node/.devcontainer/Dockerfile
image-name: devcontainers/hugo-bun-node
context: hugo-bun-node/.devcontainer
dockerfile: hugo-bun-node/.devcontainer/Dockerfile
version-tag: ${{ needs.prepare.outputs.version-tag }}
verify-command: 'bun --version && hugo version && node --version'
secrets: inherit
Loading