The Deploy page got long and the code annotations rendered inconsistently
(some as (n) numbers, some as clickable +, and a marker on a fully-commented
line made that line vanish). Fix both:
- Deploy is now a HUB (docs/deploy/index.md): the intro + collision rule +
central-deploy note, then card links to three focused sub-pages. Deploy
stays the single global-nav entry; sub-pages are not_in_nav, reached from
the hub cards.
- deploy/compose.md — compose convention, storage tiers, how deploy fills
it in, subprocess/browser knobs, checklist
- deploy/dockerfile.md — services-account image, COPY, layer caching, uv
- deploy/secrets.md — keeping secrets out of the image
- Replace code annotations with INLINE COMMENTS on the compose/Dockerfile
examples: everything visible at once, no + to click, and the commented
MOUNTS_DIR line no longer disappears.
- Update inbound links (index card, standards, workflow, environments) to
deploy/ and deploy/compose.md; nav Deploy -> deploy/index.md with
not_in_nav for the sub-pages.
Verified in-browser: hub cards link correctly, sub-pages render with visible
inline comments (0 annotation markers), left nav shows only Deploy;
mkdocs build --strict clean (validates not_in_nav + all cross-links).
Signed-off-by: disqualifier <dev@disqualifier.me>
4.8 KiB
Compose convention
The compose.yaml your repo ships. Copy it verbatim — it names nothing
repo-specific, so the deploy layer can inject identity and host paths without
collisions.
services:
svc: # generic service key — ALWAYS svc, no container_name
build: .
user: "1337:1337" # the shared services account (required)
restart: unless-stopped # required — the host unit is oneshot; this recovers crashes
environment:
HOME: /tmp
volumes:
- ${LOGS_DIR:-./logs}:/app/logs # output — host log dir, injected at deploy
- ${CONFIG_DIR:-./config}:/app/config # input — host config dir, injected at deploy
# - ${MOUNTS_DIR:-./mounts}:/app/data # optional — arbitrary host data (opt-in, see below)
- cache:/app/cache # your data — ephemeral named volume
volumes:
cache: # bare name — deploy auto-prefixes it per service
The rules
- Service key is always
svc. Never a repo-specific name, never acontainer_name— the ops tooling keys offsvc. This is what makes services collision-proof on the fleet. user: "1337:1337"andrestart: unless-stoppedare required. The first runs as the sharedservicesaccount (uid/gid 1337, fixed fleet-wide); the second lets Docker recover a crashed container (the host-side unit is oneshot).- Host paths come from
${...}variables, never hardcoded. Write to${LOGS_DIR}and${CONFIG_DIR};${MOUNTS_DIR}is optional. - Named volumes use bare names (
cache, notmyapp_cache) — deploy auto-prefixes them per service so they can't collide.
!!! tip "The container path is where your code reads and writes"
WORKDIR is /app, so the right-hand side of each volume line is the path
your code targets. If your app writes to ./cache (i.e. /app/cache), that's the
cache mount; logs go to /app/logs, config is read from /app/config. Match
the container path to what your code actually uses.
Three tiers of storage
logs(output) andconfig(input you edit) — we handle these: injected and managed at deploy time.mounts(optional) — a host dir for arbitrary read/write data. The deploy layer injectsMOUNTS_DIRand auto-creates the dir, but the mount line is opt-in: uncomment it and choose the container path yourself (it's app-specific, so it can't be auto-mounted).- Named volumes (
cache, …) — yours: anything else your service persists. Docker owns them, no host paths to manage.
!!! tip "The ${VAR:-./default} pattern"
Host paths use a variable with a local fallback. Locally, docker compose up needs
nothing set — it uses ./logs, ./config (and ./mounts if you enable it). When
deployed, the ops tooling sets the real values, so the same file works both places.
How deploy fills it in
At deploy time the tooling generates the environment your compose reads, so the
${...} variables resolve without anything from you:
LOGS_DIR # your host log dir -> ${LOGS_DIR}
CONFIG_DIR # your host config dir -> ${CONFIG_DIR}
MOUNTS_DIR # optional host data dir -> ${MOUNTS_DIR} (if you enable the mount)
Write your logs and config to those mount points and you're set. If a service won't come up or its logs aren't persisting, that's a host-side detail on our end — flag it and we'll sort it.
Subprocess and browser workloads
Bots that spawn Chrome, Xvfb, ffmpeg, or other child processes need three extra knobs:
services:
svc:
build: .
user: "1337:1337"
restart: unless-stopped
init: true # tini as PID 1 — reaps zombie subprocesses, forwards signals
shm_size: "2gb" # Chrome/headless browsers crash on Docker's default 64 MB /dev/shm
mem_limit: "4g" # bound memory — raise as worker/browser count grows
!!! warning "The PID-1 gotcha with shell-wrapper CMDs"
If your CMD is a shell-script wrapper (e.g. xvfb-run ...), it must not be
PID 1, or the real process dies on startup. init: true is exactly what fixes this
— tini takes PID 1, and your wrapper runs as a normal child.
Checklist
- service key
svc— nocontainer_name, no repo-specific names user: "1337:1337"— the sharedservicesaccountrestart: unless-stopped- host paths via
${LOGS_DIR}/${CONFIG_DIR}(and optional${MOUNTS_DIR}) — never hardcoded - named volumes with bare names (
cache, notmyapp_cache) - for browser/subprocess workloads:
init: true,shm_size,mem_limit
See Dockerfile & build for the image, and Secrets for keeping credentials out of it.