# Docker Compose Best Practices

## Volume Mounts
- Use relative paths (`./config`, `./data`) for host-side mounts — Armorer pre-creates them.
- NEVER use absolute host paths; the install dir varies per machine.
- **NEVER** use bare `${VAR}` for the host side of a volume mount. If the variable is unset
  the result is `:/container/path` which Docker rejects with `invalid spec: empty section
  between colons`. Always use a literal fallback: `${VAR:-./default}:/container/path`, or
  better yet, use a plain relative path like `./data:/container/path` and skip the variable
  entirely. This is the **#1 cause of first-run failures**.
- If the container runs as a non-root user (e.g. `node`, `postgres`), the host directory
  ownership must match the container user's UID/GID. Armorer pre-creates these directories
  with the current user's ownership.
- Prefer bind mounts for config/data the user needs to access; named volumes for databases.

## Entrypoint vs Command
- `entrypoint` defines HOW the process is invoked (e.g. `["node", "dist/index.js"]`).
- `command` defines WHAT arguments to pass (e.g. `["gateway", "--port", "18789"]`).
- If the app has separate CLI and daemon modes, define **two services**:
  - A daemon service with `command: ["gateway"]` or similar.
  - A CLI service with an explicit `entrypoint` (e.g. `["node", "dist/index.js"]`) and
    `stdin_open: true` + `tty: true` so subcommands like `setup`, `config`, `update` work
    via `docker compose run <cli-service> <subcommand>`.
- NEVER use raw IP addresses (e.g. `0.0.0.0`) for `--bind` flags if the app expects named
  values (e.g. `loopback`, `lan`, `auto`). Always check the app's README or `--help` to
  determine the correct bind syntax.

## Environment Variables
- Only reference env vars the app **actually** uses. Check `.env.example`, README, or docs.
- Use `${VAR}` or `${VAR:-default}` syntax so Docker reads from the `.env` file.
- Never invent env var names that the app doesn't recognise.
- If the app generates its own config during setup, you probably don't need env vars.

## Service Naming
- Give each service a descriptive name: `appname-gateway`, `appname-cli`, `appname-db`.
- For CLI/interactive services: set `stdin_open: true`, `tty: true`, and an explicit
  `entrypoint` so subcommands work.
- For daemon services: set `restart: unless-stopped` and `init: true`.
- Add `healthcheck` when the app exposes an HTTP endpoint or port.

## First-Time Setup
- Many apps require a setup/init step before they work. Common patterns:
  - `<app> setup` — interactive first-time configuration wizard
  - `<app> init` — initialise config files and database
  - `<app> configure` — set up API keys and preferences
- If the README mentions any setup step, add a YAML comment at the top:
  `# First run: docker compose run <cli-service> setup`
- Some apps auto-generate config files (`.env`, JSON config) during setup. Don't
  pre-create files the app expects to generate itself.

## Networking
- Don't expose ports unless the service needs external access.
- Use Docker networks for inter-service communication.
- Map only the ports mentioned in the app's docs.
- For WebSocket services, expose the WS port directly (don't put behind a proxy).

## Common Gotchas
- Images running as non-root (node:18, postgres:16) need writable volume directories.
- Use `init: true` for proper signal handling — without it, containers may not stop cleanly.
- ARM64 hosts (Raspberry Pi, Apple Silicon) need ARM-compatible images. Check if the image
  supports `linux/arm64`. If not, look for an Alpine or community ARM build.
- Docker Compose v2 uses `docker compose` (no hyphen). Never use `docker-compose` (v1).
- `build:` should point to the directory containing the Dockerfile, not the Dockerfile itself.

## Docker Compose Version Compatibility

### `env_file` Syntax (Map vs String)
Modern Docker Compose (v2.24.0+) supports defining `env_file` as a list of objects with a `required` attribute:
```yaml
env_file:
  - path: ./.env
    required: false  # Allows the file to be missing
```
Older versions (e.g., v2.15.x) will fail with:
`'env_file[x]' expected type 'string', got unconvertible type 'map[string]interface {}'`

**Fix:** Downgrade to a simple list of strings. If the file was optional (`required: false`), you MUST ensure the file exists (even if empty) to prevent Docker from crashing.
```yaml
env_file:
  - ./.env
```

### `develop` / Compose Watch
Modern Docker Compose (v2.22.0+) supports a `develop` top-level key for "Compose Watch" (hot reloading).
Older versions will fail with: `services.app Additional property develop is not allowed`.

**Fix:** Remove the entire `develop` block from the service definition. The application will still run, but you lose the hot-reloading capability.

## Common Errors
- `invalid spec: empty section between colons`: You are using a bare variable `${VAR}` in a volume
  mount but the variable is unset. Use `${VAR:-./default}` or a literal path.
