deploy: add COPY-into-image tabs, clarify volume ownership, drop mounts

- deploy.md: new 'Getting your files into the image' subsection — a
  multi-tab annotated block (single file / directory / deps-first) showing
  COPY <src> <dest> patterns, plus a warning not to COPY secrets.
- clarify volume ownership: 'Logs and config are ours; the rest is yours' —
  we inject/manage the logs + config host mounts; devs put everything else
  in named volumes Docker owns.
- remove all /srv/mounts: dropped the MOUNTS_DIR volume + env var and every
  mounts reference from the compose block, injected-vars, paths line, and
  checklist. environments.md + index.md mounts mentions cleaned too.

Verified in-browser: COPY tabs switch, volume tip renders, zero mounts refs
remain; mkdocs build --strict clean.

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-01 01:36:25 -04:00
parent 7ed388c69c
commit 8c56fd98a8
3 changed files with 58 additions and 16 deletions
+54 -12
View File
@@ -14,7 +14,7 @@
- service key is always **`svc`**
- named volumes use **bare names** (`cache`, not `myapp_cache`)
- host paths come from **`${LOGS_DIR}` / `${CONFIG_DIR}` / `${MOUNTS_DIR}`**
- host paths come from **`${LOGS_DIR}` / `${CONFIG_DIR}`**
Naming things after your repo used to cause **container-name collisions** at
fleet scale — two repos shipping the same name clashed. The generic compose
@@ -79,6 +79,45 @@ CMD ["python", "-m", "yourapp"]
3. `--system` installs into the image's Python (no venv needed — the container
*is* the isolation); reads `pyproject.toml`, no `uv.lock` required.
### Getting your files into the image
`COPY . .` grabs the whole repo, but be explicit about anything that needs its own
place — assets, templates, a config the app reads at runtime. `COPY <src> <dest>`:
`<src>` is relative to the build context (your repo), `<dest>` is a path in the
image.
=== "A single file"
```dockerfile
COPY ./config.toml /app/config.toml # (1)!
```
1. One file into a specific path. The app reads it at `/app/config.toml`.
=== "A directory"
```dockerfile
COPY ./assets /app/assets # (1)!
```
1. A whole tree. Trailing paths are dirs — `/app/assets/` mirrors `./assets/`.
=== "Deps first (layer caching)"
```dockerfile
COPY requirements.txt . # (1)!
RUN pip install --no-cache-dir -r requirements.txt
COPY . . # (2)!
```
1. Copy just the deps file first, install, **then** copy the code.
2. See [layer caching](#layer-caching) — this ordering is why.
!!! warning "Don't `COPY` secrets into the image"
Anything sensitive stays **out** of the image — no `COPY ./secrets.env`. Secrets
live on the host and are injected read-only at runtime (see [Secrets](#secrets)).
Add them to `.dockerignore` so a blanket `COPY . .` can't sweep them in.
## Your `compose.yaml`
Copy this verbatim. It names nothing repo-specific — the deploy layer fills in
@@ -95,7 +134,6 @@ services:
volumes:
- ${LOGS_DIR:-./logs}:/app/logs # (4)!
- ${CONFIG_DIR:-./config}:/app/config # (5)!
- ${MOUNTS_DIR:-./mounts}:/app/mounts
- cache:/app/cache # (6)!
volumes:
@@ -112,16 +150,21 @@ volumes:
what recovers a crashed container.
4. Logs → host, path **injected** by the deploy layer. The `:-./logs` default lets
you `docker compose up` locally with nothing set and still work.
5. Config → host, likewise injected. Same pattern for `${MOUNTS_DIR}`.
5. Config → host, likewise injected.
6. Named volume with a **bare name** — ephemeral, auto-namespaced per service at
deploy time so it can't collide.
7. Declare bare (`cache`, not `myapp_cache`). Deploy auto-prefixes it.
!!! tip "Logs and config are ours; the rest is yours"
**We handle `logs` and `config`** — the two `${...}` host mounts above are
injected and managed at deploy time. **Everything else is yours:** anything
your service needs to persist (caches, profiles, scratch) goes in a **named
volume**, which Docker owns — no host paths for you to manage.
!!! tip "The `${VAR:-./default}` pattern"
Every host path uses a variable with a local fallback. Locally, `docker compose
up` needs nothing set — it uses `./logs`, `./config`, `./mounts`. On the fleet,
the deploy layer sets the real values (below), so the same file works both
places.
Both host paths use a variable with a local fallback. Locally, `docker compose
up` needs nothing set — it uses `./logs` and `./config`. On the fleet, the
deploy layer sets the real values (below), so the same file works both places.
## How deploy fills it in
@@ -132,7 +175,6 @@ are injected — not baked into your file. You can rely on these being present:
COMPOSE_PROJECT_NAME=<workspace>-<name>
LOGS_DIR=/srv/logs/<workspace>/<name>
CONFIG_DIR=/srv/config/<workspace>/<name>
MOUNTS_DIR=/srv/mounts/<workspace>/<name>
```
Which makes ownership obvious and collisions impossible:
@@ -150,9 +192,9 @@ Which makes ownership obvious and collisions impossible:
- **`<name>`** — the git repo name, lowercased, by default. Overridable at
deploy time.
Host paths live under `/srv/<kind>/<workspace>/<name>/` (config, logs, mounts), all
owned by the `services` user (uid/gid **1337**). You never write these paths in your
compose — you read the injected `${...}` variables.
Host paths live under `/srv/<kind>/<workspace>/<name>/` (config, logs), all owned by
the `services` user (uid/gid **1337**). You never write these paths in your compose —
you read the injected `${...}` variables.
## Onboarding a service
@@ -227,7 +269,7 @@ services:
- 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}` / `${MOUNTS_DIR}` — never hardcoded
- host paths via `${LOGS_DIR}` / `${CONFIG_DIR}` — never hardcoded
- named volumes with **bare names** (`cache`, not `myapp_cache`)
- for browser/subprocess workloads: `init: true`, `shm_size`, `mem_limit`
+2 -2
View File
@@ -68,8 +68,8 @@ happens, depending on where the project runs:
```
This is how things run in production — see the [Deploy guide](deploy.md) for the
full container standard (uid 1337, mounts, layer caching, and the uv image
setup).
full container standard (uid 1337, the compose convention, layer caching, and the
uv image setup).
!!! tip "Which one?"
**Local `.venv`** for quick iteration, **Makefile** when you want repeatable
+2 -2
View File
@@ -70,7 +70,7 @@ out of it; examples use placeholders like `<workspace>`, `<project>`, and
---
How to get a project running on **rethink-net**containers, paths and
mounts, permissions, and secrets.
How to get a project running on **rethink-net**the compose convention,
the one-command deploy, and secrets.
</div>