A GitHub template that gives every project the same fast, reproducible DevContainer in under a minute, plus a full AI coding context (skills, agents, hooks) wired in automatically via harness-ai. Clone it, reopen in a container, start working no per-project setup, no manual AI tool configuration.
Opinionated by design. Reflects a personal setup use it as a starting point and adapt it once cloned.
Standard devcontainer features compile tools from source (slow, non-deterministic). This template uses multi-stage Docker builds with pre-built runtimes: Python and Node.js are copied from their official images. First build takes ~30s instead of minutes.
Configuration is split into three layers:
- BASE template-managed files (
Dockerfile,docker-compose.yml,justfile, etc.), updated viacli.sh - PROJECT
*.projectfiles committed to git, shared across the team - LOCAL
*.localfiles gitignored, personal dev overrides
| Tool | Notes | |
|---|---|---|
| Runtime | Python 3.13 | via uv, multi-stage COPY |
| Runtime | Node.js 24 | multi-stage COPY |
| Shell | zsh + Oh My Zsh | autosuggestions, syntax-highlighting, completions |
| Tasks | just | see just help |
| Hooks | pre-commit | installed on first just setup |
Optional tools (disabled by default, enable via build args):
| Tool | Build ARG |
|---|---|
| Claude Code CLI | CLAUDE_CLI_ENABLE=true |
| GitHub Copilot CLI | GITHUB_COPILOT_CLI_ENABLE=true |
| AWS CLI | AWS_CLI_ENABLE=true |
| OpenSpec | OPENSPEC_ENABLE=true |
| OpenCode | OPENCODE_ENABLE=true |
| Kind | KIND_ENABLE=true |
| LLaMA.cpp | LLAMA_CPP_ENABLE=true |
| Terraform (via tfenv) | TERRAFORM_ENABLE=true |
The harness-ai feature in devcontainer.json assembles AI skills, agents, and hooks (including token-saving layers like RTK) into the workspace at container startup. It's config-driven: postCreateCommand runs harnessai install, postStartCommand runs harnessai sync, both reading .harness-ai/config.yaml (versioned, never overwritten by this template).
See the harness-ai docs for available tools, install, behavior, and contentRepo options this template only provisions the feature and a minimal default config, it doesn't duplicate harness-ai's own documentation.
Requirements: Docker, VSCode + Dev Containers extension
Run this on terminal:
git clone <your-project> ; cd <your-project>
curl -fsSL https://raw.githubusercontent.com/FabrizioCafolla/harness-coding/main/cli.sh | bash -s -- update --force--force is required the first time: cli.sh refuses to run in a directory without .devcontainer/ unless told to create one. Drop --force on every later update once the project has been scaffolded once. Once justfile.tooling exists, use just harness-coding update --force instead (see Updating from template) — no local cli.sh, always fetched fresh from main.
Ctrl+Shift+P → Dev Containers: Reopen in ContainerThe container build runs harnessai install; every start runs harnessai sync && just setup automatically.
just help # list all commands
just gh-login # authenticate GitHub CLI
just claude-login # authenticate Claude CodeEdit .devcontainer/docker-compose.project.yml for team-wide settings:
services:
devcontainer:
build:
args:
CLAUDE_CLI_ENABLE: 'true'
AWS_CLI_ENABLE: 'true'Or .devcontainer/docker-compose.local.yml for local-only overrides (gitignored).
.devcontainer/scripts/setup-devcontainer.project.shteam-wide, committed.devcontainer/scripts/setup-devcontainer.local.shlocal dev, gitignored
Installed tool versions (Claude Code, Copilot CLI, OpenCode, OpenSpec) are checked and updated
on every container start; each update is capped with a 60s timeout so a stuck one (e.g. an
interactive Copilot auth prompt) can't hang the whole setup. Run just tools-update to re-run
the same check on demand without a full container restart.
justfile.projectteam commands, committedjustfile.locallocal commands, gitignored
.env.projectshared env vars, committed.envlocal overrides, gitignored
Both are sourced automatically in each shell via .zshrc.
cli.sh is never copied into a consumer project — it's always fetched fresh from main (or a
pinned --ref). Once justfile.tooling is scaffolded in, use the harness-coding target; any
cli.sh args pass straight through:
just harness-coding check # see what changed upstream
just harness-coding update --force # apply updates (--force required on first bootstrap)Or via curl directly (no justfile.tooling yet):
curl -fsSL https://raw.githubusercontent.com/FabrizioCafolla/harness-coding/main/cli.sh | bash -s -- check
curl -fsSL https://raw.githubusercontent.com/FabrizioCafolla/harness-coding/main/cli.sh | bash -s -- update --forceOptions: --ref REF, --force, --workspace DIR
| Category | Files | Behavior |
|---|---|---|
| REPLACE | docker-compose.yml, setup-devcontainer.sh, justfile.tooling |
Overwritten on update |
| MARKER | AGENTS.md, .pre-commit-config.yaml, .gitignore, Dockerfile, .zshrc, justfile |
Only the [harness-coding:START/END] block is updated |
| DIFF-ONLY | devcontainer.json |
Diff shown, not auto-applied (--force to override) |
| NEVER-TOUCH | *.project, *.local, README.md, .harness-ai/config.yaml, .harness-coding/config.yml |
Created once if missing, never overwritten |
| DEPRECATED | update-devcontainer.sh, justfile.test, justfile.private, others |
Removed automatically on update |
cli.sh itself is not a consumer file — never copied down, only curled fresh (directly, or via
just harness-coding above). .harness-coding/config.yml sets the template version (git ref)
cli.sh resolves against; --ref on the CLI always wins. Every update writes .harness-coding/lock
(resolved SHA) and .harness-coding/manifest.json (per-file category + hash), both versioned in
git so the template state is shared and reproducible across the team.
.devcontainer/
├── Dockerfile # MARKER
├── docker-compose.yml # REPLACE
├── docker-compose.project.yml # NEVER-TOUCH (team overrides)
├── docker-compose.local.yml # NEVER-TOUCH (local overrides, gitignored)
├── devcontainer.json # DIFF-ONLY
├── configs/
│ ├── .zshrc # MARKER
│ └── .aws/
├── cache/ # mounted volumes (.claude, .copilot, .opencode, .llama)
└── scripts/
├── setup-devcontainer.sh # REPLACE
├── setup-devcontainer.project.sh # NEVER-TOUCH
└── setup-devcontainer.local.sh # NEVER-TOUCH (gitignored)
justfile # MARKER (soli import)
justfile.tooling # REPLACE
justfile.project # NEVER-TOUCH
justfile.local # NEVER-TOUCH (gitignored)
.env.project # NEVER-TOUCH
.env # gitignored
.harness-ai/config.yaml # NEVER-TOUCH (harness-ai provisioning)
.harness-coding/config.yml # NEVER-TOUCH (cli.sh ref pin)
.harness-coding/lock # written by cli.sh update, versioned
.harness-coding/manifest.json # written by cli.sh update, versioned
AGENTS.md # MARKER
.pre-commit-config.yaml # MARKER