A single-file wrapper around docker compose that you copy into a project, next to its docker-compose.yaml. It pins the project name, picks the right env file, and gives you short commands for the everyday build/start/stop cycle.
| Platform | File | Run as |
|---|---|---|
| Linux / macOS | compose-helper.sh |
./compose-helper.sh <command> |
| Windows | compose-helper.ps1 |
.\compose-helper.ps1 <command> |
Both scripts are feature-equivalent. Examples below use ./compose-helper.sh; substitute .\compose-helper.ps1 on Windows.
⚠️ For local development only. Do not use in production or CI/CD pipelines —downremoves volumes, env files are sourced and exported into the process, and there is no access control or dry-run mode.
Installation is agent-guided only — there are no install scripts. Paste this into your coding agent (Codex, Claude Code, Antigravity, or Cline; Cursor picks the skill up through the shared global copies):
Fetch https://raw.githubusercontent.com/jpbaking/compose-helper/main/AGENT-INSTALL.md and follow its instructions exactly to install compose-helper. Merge with — never blindly overwrite — any existing global or project instruction files, and report every file you created or changed.
The procedure in AGENT-INSTALL.md has two parts: the agent rule & skill install user-global (once per user, into each harness's global discovery paths — nothing is added to your repos), and the wrapper script + compose-helper.env install per project as committed files. The agent acquires the sources itself (git clone, repo zip, or gh) and merges with — never overwrites — your existing instruction files. Re-run anytime to update.
Prefer to install by hand? See Manual setup.
Shorthand commands take exactly one argument. Anything else passes straight through to docker compose — see Pass-through.
| Command | What it does | Caution |
|---|---|---|
up |
Build images → start detached → follow logs | ⏳ blocks on logs; Ctrl-C stops following, containers keep running |
rebuild |
Build images → start detached | |
build |
Build images only (no start) | |
pull |
Pull images | |
start |
Start detached — no build, no pull | stale images if you changed a Dockerfile |
restart |
Stop → start detached — no build, no pull | |
stop |
Stop containers, remove orphans — data volumes kept | |
down |
Stop containers, remove orphans and delete named volumes | 🗑️ destroys data — databases, caches, everything |
logs |
Follow logs from the last N lines | ⏳ blocks until Ctrl-C |
Two commands to remember:
stopvsdown:stoppreserves your data.downis a clean-slate reset that deletes named volumes. If in doubt, usestop.startnever builds: after changing a Dockerfile, a build context, ordocker-compose.yamlitself, userebuild.
A typical day:
./compose-helper.sh up # morning: build, start, watch logs
# Ctrl-C when you've seen enough — containers keep running
./compose-helper.sh logs # peek at logs again later
./compose-helper.sh rebuild # after code/Dockerfile changes
./compose-helper.sh stop # evening: stop, keep dataIf you are an AI agent (or writing automation) operating in a project that contains this script, follow these rules. Machine-readable versions ship as a shared rule (rules/shared/compose-helper.md) and a portable Agent Skill (skills/shared/compose-helper/) — see Agent rule & skill.
- Always use this script for compose operations — never
docker composeordocker-composedirectly. Calling docker compose yourself can create a duplicate project with a different name. - Never run
downunless the user explicitly asked to wipe or reset data. It deletes named volumes. To stop containers, runstop. - Never run
uporlogs(the one-argument shorthands). Both follow logs forever and will hang your session. Use the alternatives below. - Two or more arguments bypass the shorthands and go straight to
docker compose. Use that deliberately for anything not in the table — and rememberup -dis raw docker compose, not theupshorthand. startandrestartnever build. After editing a Dockerfile, build context, ordocker-compose.yaml, runrebuild.- Run the script as-is from where it lives. Do not
cdelsewhere first, and do not add your own-por-fflags — it pins those itself.
| Goal | ✅ Run this | ❌ Not this |
|---|---|---|
| Build and start everything | ./compose-helper.sh rebuild |
up — blocks forever |
| Read logs | ./compose-helper.sh logs --tail=100 |
logs — blocks forever |
| Logs for one service | ./compose-helper.sh logs --tail=100 <service> |
docker logs <container> |
| Stop containers | ./compose-helper.sh stop |
down — deletes data |
| List running services | ./compose-helper.sh ps |
docker ps |
| Validate the compose file | ./compose-helper.sh --profile build config --quiet |
docker compose config |
| Shell into a service | ./compose-helper.sh exec <service> sh |
docker exec … |
Creating or editing docker-compose.yaml in a project that uses this script? Follow the --profile build convention.
Every invocation expands to:
docker compose -p <project> -f <compose-file> [--env-file <env-file>] <args>
<project>— the directory name the script lives in, orDCH_PROJECT_NAMEfromcompose-helper.envif set. Pinning it prevents docker compose from deriving the name from the caller's working directory, which silently creates duplicate projects when invoked from different places or via symlink.<compose-file>—docker-compose.yaml, falling back todocker-compose.yml. The script errors out if neither exists.<env-file>— the first of.env(project root) or.config/.envthat exists. If neither exists, no--env-fileflag is added.
The bash script resolves symlinks to its real location, so you can symlink it anywhere and it still operates on its own project folder. (PowerShell does not resolve symlinks — keep compose-helper.ps1 physically in the project directory.)
Both scripts prefer the compose v2 plugin (docker compose) and fall back to the standalone v1 binary (docker-compose) if the plugin is not available.
Passing two or more arguments — or a single argument that isn't a shorthand — routes the call directly to docker compose, still with the pinned project name, compose file, and env file:
./compose-helper.sh up # shorthand: build → start → follow logs
./compose-helper.sh up -d # pass-through: raw `docker compose up -d`
./compose-helper.sh ps # pass-through: `docker compose ps`
./compose-helper.sh logs --tail=50 # pass-through: bounded logs, returns immediately
./compose-helper.sh exec api sh # pass-through: shell into the `api` serviceThis is the intended escape hatch: any docker compose feature works through the script, so there is never a reason to call docker compose directly in a project that has it.
- Copy
compose-helper.sh(Linux/macOS) orcompose-helper.ps1(Windows) into your project directory, alongsidedocker-compose.yaml. - Linux/macOS: make it executable —
chmod +x compose-helper.sh - Optionally copy
compose-helper.env.exampletocompose-helper.envand adjust values.
my-project/
├── compose-helper.sh # or compose-helper.ps1 on Windows
├── compose-helper.env # optional — configures the script itself
├── docker-compose.yaml
└── .env # optional — variables for docker-compose.yaml
Windows: if PowerShell blocks the script, allow local scripts once:
Set-ExecutionPolicy -Scope CurrentUser RemoteSignedScript behaviour is controlled by compose-helper.env, placed alongside the script (see compose-helper.env.example for a ready-to-copy template). Values there override variables inherited from your shell.
| Variable | Default | Description |
|---|---|---|
DCH_PROJECT_NAME |
(dir name) | Override the docker compose project name |
DCH_STOP_TIMEOUT |
30 |
Seconds to wait for graceful shutdown before killing containers |
DCH_LOGS_TAIL |
10 |
Lines of existing log output shown before following live output |
| File | Consumed by | Purpose |
|---|---|---|
.env (or .config/.env) |
docker compose | ${VAR} substitution inside docker-compose.yaml |
compose-helper.env |
the script | Script settings only (DCH_* variables above) |
Variables your containers need go in .env. Never put them in compose-helper.env — and vice versa.
The build, rebuild, and up commands run docker compose --profile build build --pull. This supports a pattern that separates services that produce a local image from services that run:
services:
# BUILDER: only exists to produce an image. Built by `build`/`rebuild`/`up`,
# never started.
my-app-builder:
profiles: [build] # visible only when --profile build is passed
build: ./my-app # directory containing the Dockerfile
image: my-app:local # required — the tag the build produces
# RUNTIME: consumes the image. No build: block.
my-app:
image: my-app:local # must match the builder's tag exactly
# Pulled images need no profile and no builder.
postgres:
image: postgres:16Rules when writing a compose file for this pattern:
- Every service with a
build:block getsprofiles: [build]and an explicitimage:tag. - Runtime services reference that same tag and have no
build:block. - Never
depends_ona builder service — builders are built, never started. - The profile name must be exactly
build; the scripts hard-code it.
Why bother: start/restart stay fast and side-effect-free (they never compile anything), while build --pull refreshes base images instead of reusing stale cached layers.
If you don't use this pattern, the --profile build flag is harmless — it simply targets no services during the build step.
The repo ships universal assets that teach AI coding agents — Codex, Claude Code, Google Antigravity, Cline, and Cursor — to use compose-helper safely:
| Canonical source | What it does | Installed to |
|---|---|---|
rules/shared/compose-helper.md |
Always-loaded rule: the same hard rules as Rules for AI agents, in the agent's context on every turn, self-gated on the wrapper script's presence | ~/.agents/rules/, ~/.gemini/config/rules/, Cline's global Rules dir, plus pointer blocks merged into ~/.codex/AGENTS.md and ~/.claude/CLAUDE.md |
skills/shared/compose-helper/SKILL.md |
On-demand Agent Skill: creating/modifying docker-compose.yaml with the --profile build convention, plus an edit → build → verify workflow |
~/.agents/skills/ (Codex), ~/.claude/skills/ (Claude Code), ~/.gemini/config/skills/ (Antigravity), ~/.cline/skills/ (Cline) |
AGENT-INSTALL.md sets these up user-global by default: the skill goes to each harness's global skills directory and the rule to its global rules location, self-gated so it only activates in projects that contain the wrapper script. Your repos carry only the committed compose-helper.sh / .ps1 and compose-helper.env; teammates who want the rule & skill install them for themselves (project-level install remains an explicit opt-in). Invocation: ask your agent to use the compose-helper skill — Codex selects it via a $ mention, while Claude Code, Antigravity, and Cline also support /compose-helper.
0BSD — do whatever you want with it.