🤖🙌👤 A warm, nerdy, living document. Work In Progress — we add more recommendations over time and point agents, PRs, and issues here as a shared reference for "does this container respect the person running it?"
Status: WIP / continuously growing
Scope: Practical, low-friction conventions that make self-hosted Docker apps
pleasant to run — especially single-container "just docker compose up" projects.
How to use: Link to this gist from PRs/issues that implement one of these
recommendations, and add new sections as patterns prove themselves in the wild.
Most self-hosting friction is not about features — it's about the first five minutes. Port clashes, hardcoded values, surprise LAN exposure, and "where does my data go?" are what make someone close the tab. These recommendations bias toward sensible non-breaking defaults that are overridable without editing tracked files.
Guiding principles:
- Non-breaking first — improvements should keep existing defaults working. Add opt-in flexibility, don't change behavior out from under people.
- Overridable without editing tracked files — env vars +
.env, not "fork-and-edit". - Discoverable — every knob is documented where a newcomer will actually look (README Quick Start), not only in a config reference.
- Least surprise — don't expose to the LAN, don't auto-restart forever, don't write to the host tree unless the user asked.
Problem: A hardcoded "3000:3000" in docker-compose.yml collides with every
other workload already using 3000, and there is no CLI flag on docker compose up to remap a published port. The user is forced to edit a tracked file.
Fix: Parameterize the host side with a project-prefixed variable and a default that preserves current behavior:
ports:
# The container always listens on 3000 internally (see PORT below).
- "${AMETHYST_PORT:-3000}:3000"Now all three of Compose's standard mechanisms work, in precedence order:
- Shell env —
AMETHYST_PORT=8137 docker compose up - A
.envfile in the project root (auto-loaded, gitignored) - The
:-3000default baked in — zero-config, unchanged for existing users
Why this shape:
- Only the host port changes; the container port, the internal
PORTenv, and the healthcheck all keep referencing3000. Nothing inside the container moves. - Keep the default at the project's current port (here
3000) so bookmarks, README, and existing deployments are untouched — the change is purely additive. - Use a project-prefixed name (
AMETHYST_PORT, notPORT) so it can't be confused with the app's own internal listen port.
Anti-patterns:
VAR=8137 docker compose updoes nothing unless the compose file literally contains${VAR}— Compose only substitutes where you opted in.- There is no
docker compose up --publishflag; don't suggest one. - Don't change the default port in the same PR — that's a breaking change masquerading as a convenience.
Implemented in the wild:
Problem: "3000:3000" binds 0.0.0.0, silently publishing the app to the
whole LAN. Most first-run users want localhost only.
Direction (candidate — not yet field-tested): consider documenting a
127.0.0.1:${PORT}:3000 form for laptop/dev use, and treat all-interfaces
binding as the deliberate "I'm deploying this" choice. Needs a clean way to flip
between the two without two compose files. (WIP — to be fleshed out.)
Already partly done in apps that ship ${SESSION_SECRET:-please-change-me}. The
pattern is good: the app boots with zero config, but the default value is
obviously a placeholder that screams "change me in production". Extend the same
treatment to every secret, and document overriding via .env.
(WIP — collect concrete examples.)
- Healthchecks that reference the container port, never the host mapping.
restart: unless-stoppedvsno— don't surprise people with a service that resurrects on reboot during a one-off trial.- Named volumes vs host bind mounts — make "where is my data" obvious and
down -vthe documented reset. - A committed
.env.exampleas living documentation of every knob. - First-run UX: the "first account becomes admin" pattern and how to communicate it.
Maintained as a shared reference. Contributions, corrections, and links to real-world PRs welcome — that's the point of the WIP.