Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save gwpl/f0718e7bec841c1978cee59814fe4a01 to your computer and use it in GitHub Desktop.

Select an option

Save gwpl/f0718e7bec841c1978cee59814fe4a01 to your computer and use it in GitHub Desktop.
Docker Self-Hosting UX Recommendations (WIP, living doc) — non-breaking, overridable-without-editing-tracked-files conventions for self-hosted containers. Agents/PRs/issues point here.

Docker Self-Hosting — User-Experience Recommendations (WIP)

🤖🙌👤 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.


Why this exists

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.

Recommendation 1 — Make the published host port overridable

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:

  1. Shell env — AMETHYST_PORT=8137 docker compose up
  2. A .env file in the project root (auto-loaded, gitignored)
  3. The :-3000 default baked in — zero-config, unchanged for existing users

Why this shape:

  • Only the host port changes; the container port, the internal PORT env, and the healthcheck all keep referencing 3000. 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, not PORT) so it can't be confused with the app's own internal listen port.

Anti-patterns:

  • VAR=8137 docker compose up does nothing unless the compose file literally contains ${VAR} — Compose only substitutes where you opted in.
  • There is no docker compose up --publish flag; 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:


Recommendation 2 — Default to local-only binding; make LAN exposure opt-in

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.)


Recommendation 3 — Parameterize secrets with safe-but-loud defaults

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.)


Backlog / ideas to write up

  • Healthchecks that reference the container port, never the host mapping.
  • restart: unless-stopped vs no — 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 -v the documented reset.
  • A committed .env.example as 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment