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>
107 lines
4.8 KiB
Markdown
107 lines
4.8 KiB
Markdown
# 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.
|
|
|
|
```yaml
|
|
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 a
|
|
`container_name` — the ops tooling keys off `svc`. This is what makes services
|
|
collision-proof on the fleet.
|
|
- **`user: "1337:1337"` and `restart: unless-stopped` are required.** The first runs
|
|
as the shared `services` account (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`, not `myapp_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) and **`config`** (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
|
|
**injects `MOUNTS_DIR` and 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:
|
|
|
|
```yaml
|
|
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` — **no `container_name`**, no repo-specific names
|
|
- `user: "1337:1337"` — the shared `services` account
|
|
- `restart: unless-stopped`
|
|
- host paths via `${LOGS_DIR}` / `${CONFIG_DIR}` (and optional `${MOUNTS_DIR}`) — never
|
|
hardcoded
|
|
- named volumes with **bare names** (`cache`, not `myapp_cache`)
|
|
- for browser/subprocess workloads: `init: true`, `shm_size`, `mem_limit`
|
|
|
|
See **[Dockerfile & build](dockerfile.md)** for the image, and **[Secrets](secrets.md)**
|
|
for keeping credentials out of it.
|