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
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ ifdef WORKSPACE_ROOT
ANSIBLE_ENV := WORKSPACE_ROOT=$(WORKSPACE_ROOT)
endif

.PHONY: help check apply create-missing syntax test
.PHONY: help check apply create-missing syntax test rm-bak

help:
@printf "Targets:\n"
@printf " make check Preview changes with --check --diff\n"
@printf " make apply Apply rendered devcontainer files\n"
@printf " make create-missing Create missing .devcontainer directories/files\n"
@printf " make syntax Run Ansible syntax check\n"
@printf " make rm-bak Find all .devcontainer-bak-* subdirectories and rm -rf them\n"
@printf " make test Render group_vars/all.example.yml into tests/\n"
@printf "\nOverride workspace root with WORKSPACE_ROOT=/path/to/workspaces.\n"

Expand All @@ -31,6 +32,11 @@ create-missing:
syntax:
$(ANSIBLE_ENV) $(ANSIBLE_PLAYBOOK) --syntax-check $(PLAYBOOK)

rm-bak:
ifndef WORKSPACE_ROOT
$(error WORKSPACE_ROOT is not defined)
endif
find ${WORKSPACE_ROOT} -type d -name ".devcontainer-bak-*" -exec rm -rf {} +

test:
$(ANSIBLE_PLAYBOOK) --diff $(PLAYBOOK) -e @$(EXAMPLE_VARS) -e workspace_root=$(TEST_WORKSPACE_ROOT) -e devcontainer_sync_create_missing=true -e devcontainer_sync_backup=false -e devcontainer_sync_backup_existing_dir=false
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ or: `ansible-playbook --check --diff playbook.yml`

- Scalar/string/bool keys are replaced by the per-container value, if it exists. This includes `image`, `post_start_command`, `install`, `name`...

- _Except!_ The `install` script templates into `roles/devcontainer_sync/templates/install.sh.j2`. As an example (and because it's _my_ preferred default), this template installs common AI tools into every devcontainer. So, if you were wondering why building the devcontainer gave you a copy of Claude and Codex, that's why. You can change the template to suit your preferences.

### 3. When the diff looks right, apply it with:

```bash
Expand Down
29 changes: 22 additions & 7 deletions group_vars/all.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,24 @@ workspace_root: "{{ lookup('env', 'WORKSPACE_ROOT') | default('~/dev/', true) }}
devcontainer_sync_create_missing: false

devcontainer_defaults:
image: mcr.microsoft.com/devcontainers/base:jammy
image: mcr.microsoft.com/devcontainers/base:resolute
post_start_command: sudo chown -R vscode:vscode /home/vscode/.claude*
post_create_command: bash .devcontainer/install.sh
# chowns and symlinks mounted directories
# runs devcontainer install
post_create_command: >-
sudo chown $USER:$USER ${containerWorkspaceFolder}/.claude &&

sudo chown $USER:$USER /opt/claude-code-config &&
mkdir -p /opt/claude-code-config/.claude &&
touch /opt/claude-code-config/.claude.json &&

ln -s /opt/claude-code-config/.claude.json $HOME/.claude.json &&
ln -s /opt/claude-code-config/.claude $HOME/.claude &&

sudo chown $USER:$USER /opt/.codex &&
ln -s /opt/.codex $HOME/.codex &&

bash .devcontainer/install.sh
post_attach_command: ""
install: ""
# note: the following keys get merged. see README.
Expand All @@ -17,14 +32,14 @@ devcontainer_defaults:
- Anthropic.claude-code
- openai.chatgpt
mounts:
- source=claude_config_${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume
- source=claude_config,target=/home/vscode/.claude,type=volume
- source=codex_config,target=/home/vscode/.codex,type=volume
- source=claude-code-config,target=/opt/claude-code-config,type=volume
- source=claude-scope-project-${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume
- source=codex-config,target=/opt/.codex,type=volume

devcontainers:
- name: java-example
path: java-example/.devcontainer/devcontainer.json
image: mcr.microsoft.com/devcontainers/java:3-25-trixie
image: mcr.microsoft.com/devcontainers/java:25
features:
ghcr.io/devcontainers/features/java:1:
version: none
Expand All @@ -34,7 +49,7 @@ devcontainers:
curl -sL -o cfr.jar 'https://www.benf.org/other/cfr/cfr-0.152.jar'
- name: rust-example
path: rust-example/.devcontainer/devcontainer.json
image: mcr.microsoft.com/devcontainers/rust:2-1-trixie
image: mcr.microsoft.com/devcontainers/rust:2-1
install: |
sudo apt update
sudo -n apt-get install -y --no-install-recommends default-jre-headless
Expand Down
10 changes: 10 additions & 0 deletions roles/devcontainer_sync/templates/install.sh.j2
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
#!/bin/bash

# This template provides a paved road for wrapping all devcontainer installers.

# For example, to install claude-code CLI in every devcontainer, use:
curl -fsSL https://claude.ai/install.sh | bash

# And Codex:
export CODEX_NON_INTERACTIVE=true
curl -fsSL https://chatgpt.com/codex/install.sh | sh

{{ devcontainer.install }}
58 changes: 44 additions & 14 deletions tests/golden_output
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,27 @@
"version": "none"
}
},
"image": "mcr.microsoft.com/devcontainers/java:3-25-trixie",
"image": "mcr.microsoft.com/devcontainers/java:25",
"mounts": [
"source=claude_config_${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=claude_config,target=/home/vscode/.claude,type=volume",
"source=codex_config,target=/home/vscode/.codex,type=volume"
"source=claude-code-config,target=/opt/claude-code-config,type=volume",
"source=claude-scope-project-${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=codex-config,target=/opt/.codex,type=volume"
],
"name": "java-example",
"postCreateCommand": "bash .devcontainer/install.sh",
"postCreateCommand": "sudo chown $USER:$USER ${containerWorkspaceFolder}/.claude &&\nsudo chown $USER:$USER /opt/claude-code-config && mkdir -p /opt/claude-code-config/.claude && touch /opt/claude-code-config/.claude.json &&\nln -s /opt/claude-code-config/.claude.json $HOME/.claude.json && ln -s /opt/claude-code-config/.claude $HOME/.claude &&\nsudo chown $USER:$USER /opt/.codex && ln -s /opt/.codex $HOME/.codex &&\nbash .devcontainer/install.sh",
"postStartCommand": "sudo chown -R vscode:vscode /home/vscode/.claude*"
}
#!/bin/bash

# This template provides a paved road for wrapping all devcontainer installers.

# For example, to install claude-code CLI in every devcontainer, use:
curl -fsSL https://claude.ai/install.sh | bash

# And Codex:
export CODEX_NON_INTERACTIVE=true
curl -fsSL https://chatgpt.com/codex/install.sh | sh

curl -sL -o cfr.jar 'https://www.benf.org/other/cfr/cfr-0.152.jar'
{
"containerEnv": {},
Expand Down Expand Up @@ -64,16 +74,26 @@ curl -sL -o cfr.jar 'https://www.benf.org/other/cfr/cfr-0.152.jar'
},
"image": "mcr.microsoft.com/devcontainers/python:3.14",
"mounts": [
"source=claude_config_${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=claude_config,target=/home/vscode/.claude,type=volume",
"source=codex_config,target=/home/vscode/.codex,type=volume"
"source=claude-code-config,target=/opt/claude-code-config,type=volume",
"source=claude-scope-project-${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=codex-config,target=/opt/.codex,type=volume"
],
"name": "python-example",
"postAttachCommand": "pip install -r ${containerWorkspaceFolder}/requirements.txt",
"postCreateCommand": "bash .devcontainer/install.sh",
"postCreateCommand": "sudo chown $USER:$USER ${containerWorkspaceFolder}/.claude &&\nsudo chown $USER:$USER /opt/claude-code-config && mkdir -p /opt/claude-code-config/.claude && touch /opt/claude-code-config/.claude.json &&\nln -s /opt/claude-code-config/.claude.json $HOME/.claude.json && ln -s /opt/claude-code-config/.claude $HOME/.claude &&\nsudo chown $USER:$USER /opt/.codex && ln -s /opt/.codex $HOME/.codex &&\nbash .devcontainer/install.sh",
"postStartCommand": "sudo chown -R vscode:vscode /home/vscode/.claude*"
}
#!/bin/bash

# This template provides a paved road for wrapping all devcontainer installers.

# For example, to install claude-code CLI in every devcontainer, use:
curl -fsSL https://claude.ai/install.sh | bash

# And Codex:
export CODEX_NON_INTERACTIVE=true
curl -fsSL https://chatgpt.com/codex/install.sh | sh

sudo apt update
sudo apt install -y --no-install-recommends ansible
{
Expand All @@ -86,16 +106,26 @@ sudo apt install -y --no-install-recommends ansible
]
}
},
"image": "mcr.microsoft.com/devcontainers/rust:2-1-trixie",
"image": "mcr.microsoft.com/devcontainers/rust:2-1",
"mounts": [
"source=claude_config_${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=claude_config,target=/home/vscode/.claude,type=volume",
"source=codex_config,target=/home/vscode/.codex,type=volume"
"source=claude-code-config,target=/opt/claude-code-config,type=volume",
"source=claude-scope-project-${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=codex-config,target=/opt/.codex,type=volume"
],
"name": "rust-example",
"postCreateCommand": "bash .devcontainer/install.sh",
"postCreateCommand": "sudo chown $USER:$USER ${containerWorkspaceFolder}/.claude &&\nsudo chown $USER:$USER /opt/claude-code-config && mkdir -p /opt/claude-code-config/.claude && touch /opt/claude-code-config/.claude.json &&\nln -s /opt/claude-code-config/.claude.json $HOME/.claude.json && ln -s /opt/claude-code-config/.claude $HOME/.claude &&\nsudo chown $USER:$USER /opt/.codex && ln -s /opt/.codex $HOME/.codex &&\nbash .devcontainer/install.sh",
"postStartCommand": "sudo chown -R vscode:vscode /home/vscode/.claude*"
}
#!/bin/bash

# This template provides a paved road for wrapping all devcontainer installers.

# For example, to install claude-code CLI in every devcontainer, use:
curl -fsSL https://claude.ai/install.sh | bash

# And Codex:
export CODEX_NON_INTERACTIVE=true
curl -fsSL https://chatgpt.com/codex/install.sh | sh

sudo apt update
sudo -n apt-get install -y --no-install-recommends default-jre-headless
10 changes: 5 additions & 5 deletions tests/java-example/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
"version": "none"
}
},
"image": "mcr.microsoft.com/devcontainers/java:3-25-trixie",
"image": "mcr.microsoft.com/devcontainers/java:25",
"mounts": [
"source=claude_config_${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=claude_config,target=/home/vscode/.claude,type=volume",
"source=codex_config,target=/home/vscode/.codex,type=volume"
"source=claude-code-config,target=/opt/claude-code-config,type=volume",
"source=claude-scope-project-${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=codex-config,target=/opt/.codex,type=volume"
],
"name": "java-example",
"postCreateCommand": "bash .devcontainer/install.sh",
"postCreateCommand": "sudo chown $USER:$USER ${containerWorkspaceFolder}/.claude &&\nsudo chown $USER:$USER /opt/claude-code-config && mkdir -p /opt/claude-code-config/.claude && touch /opt/claude-code-config/.claude.json &&\nln -s /opt/claude-code-config/.claude.json $HOME/.claude.json && ln -s /opt/claude-code-config/.claude $HOME/.claude &&\nsudo chown $USER:$USER /opt/.codex && ln -s /opt/.codex $HOME/.codex &&\nbash .devcontainer/install.sh",
"postStartCommand": "sudo chown -R vscode:vscode /home/vscode/.claude*"
}
10 changes: 10 additions & 0 deletions tests/java-example/.devcontainer/install.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
#!/bin/bash

# This template provides a paved road for wrapping all devcontainer installers.

# For example, to install claude-code CLI in every devcontainer, use:
curl -fsSL https://claude.ai/install.sh | bash

# And Codex:
export CODEX_NON_INTERACTIVE=true
curl -fsSL https://chatgpt.com/codex/install.sh | sh

curl -sL -o cfr.jar 'https://www.benf.org/other/cfr/cfr-0.152.jar'
8 changes: 4 additions & 4 deletions tests/python-example/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@
},
"image": "mcr.microsoft.com/devcontainers/python:3.14",
"mounts": [
"source=claude_config_${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=claude_config,target=/home/vscode/.claude,type=volume",
"source=codex_config,target=/home/vscode/.codex,type=volume"
"source=claude-code-config,target=/opt/claude-code-config,type=volume",
"source=claude-scope-project-${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=codex-config,target=/opt/.codex,type=volume"
],
"name": "python-example",
"postAttachCommand": "pip install -r ${containerWorkspaceFolder}/requirements.txt",
"postCreateCommand": "bash .devcontainer/install.sh",
"postCreateCommand": "sudo chown $USER:$USER ${containerWorkspaceFolder}/.claude &&\nsudo chown $USER:$USER /opt/claude-code-config && mkdir -p /opt/claude-code-config/.claude && touch /opt/claude-code-config/.claude.json &&\nln -s /opt/claude-code-config/.claude.json $HOME/.claude.json && ln -s /opt/claude-code-config/.claude $HOME/.claude &&\nsudo chown $USER:$USER /opt/.codex && ln -s /opt/.codex $HOME/.codex &&\nbash .devcontainer/install.sh",
"postStartCommand": "sudo chown -R vscode:vscode /home/vscode/.claude*"
}
10 changes: 10 additions & 0 deletions tests/python-example/.devcontainer/install.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
#!/bin/bash

# This template provides a paved road for wrapping all devcontainer installers.

# For example, to install claude-code CLI in every devcontainer, use:
curl -fsSL https://claude.ai/install.sh | bash

# And Codex:
export CODEX_NON_INTERACTIVE=true
curl -fsSL https://chatgpt.com/codex/install.sh | sh

sudo apt update
sudo apt install -y --no-install-recommends ansible
10 changes: 5 additions & 5 deletions tests/rust-example/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
]
}
},
"image": "mcr.microsoft.com/devcontainers/rust:2-1-trixie",
"image": "mcr.microsoft.com/devcontainers/rust:2-1",
"mounts": [
"source=claude_config_${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=claude_config,target=/home/vscode/.claude,type=volume",
"source=codex_config,target=/home/vscode/.codex,type=volume"
"source=claude-code-config,target=/opt/claude-code-config,type=volume",
"source=claude-scope-project-${localWorkspaceFolderBasename},target=${containerWorkspaceFolder}/.claude,type=volume",
"source=codex-config,target=/opt/.codex,type=volume"
],
"name": "rust-example",
"postCreateCommand": "bash .devcontainer/install.sh",
"postCreateCommand": "sudo chown $USER:$USER ${containerWorkspaceFolder}/.claude &&\nsudo chown $USER:$USER /opt/claude-code-config && mkdir -p /opt/claude-code-config/.claude && touch /opt/claude-code-config/.claude.json &&\nln -s /opt/claude-code-config/.claude.json $HOME/.claude.json && ln -s /opt/claude-code-config/.claude $HOME/.claude &&\nsudo chown $USER:$USER /opt/.codex && ln -s /opt/.codex $HOME/.codex &&\nbash .devcontainer/install.sh",
"postStartCommand": "sudo chown -R vscode:vscode /home/vscode/.claude*"
}
10 changes: 10 additions & 0 deletions tests/rust-example/.devcontainer/install.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
#!/bin/bash

# This template provides a paved road for wrapping all devcontainer installers.

# For example, to install claude-code CLI in every devcontainer, use:
curl -fsSL https://claude.ai/install.sh | bash

# And Codex:
export CODEX_NON_INTERACTIVE=true
curl -fsSL https://chatgpt.com/codex/install.sh | sh

sudo apt update
sudo -n apt-get install -y --no-install-recommends default-jre-headless