Template
feat: make "only the app publishes a port" a binding rule
A published database port puts the data on the public internet, and Docker's own iptables rules bypass ufw — the server looks firewalled while the port is open. Previously this was only a comment in compose.yaml, which an agent adding a db service later is free to ignore. Anchored at every point where a service could be added or shipped: - rules/security.md: the rule, the reasoning, and the audit commands - rules/backend.md + architecture SKILL: constraint when picking the data layer - agents/backend-dev.md: non-negotiable for any service it adds - agents/qa-engineer.md: a published port is a Critical finding - skills/deploy: blocking pre-flight audit - compose.yaml: db example now uses expose:, with depends_on healthcheck - docs/DEPLOYMENT.md + README: SSH tunnel instead of publishing for debugging
This commit is contained in:
+33
-3
@@ -82,14 +82,44 @@ to `HEALTH_TIMEOUT` seconds (default 120). If the container never turns healthy
|
||||
prints the last 50 log lines, resets to the previous commit, rebuilds and restarts
|
||||
that — so a bad deploy self-heals instead of leaving the site down.
|
||||
|
||||
## Port exposure
|
||||
|
||||
**Only the app service publishes a port, and only on loopback.** Everything else —
|
||||
database, cache, queue, admin UI — gets no `ports:` entry. Services reach each other
|
||||
by name over the Compose network, which needs nothing published:
|
||||
|
||||
```
|
||||
DATABASE_URL=postgres://user:pass@db:5432/app
|
||||
```
|
||||
|
||||
`ports: "5432:5432"` binds `0.0.0.0`, which means the database is on the public
|
||||
internet. And Docker writes its own nat/DOCKER-USER iptables rules, so **ufw does not
|
||||
block it** — the server looks firewalled while the port is open. This is a routine
|
||||
way self-hosted stacks get breached.
|
||||
|
||||
Need access for debugging? Do not publish the port. Use an SSH tunnel:
|
||||
|
||||
```bash
|
||||
ssh -L 5432:localhost:5432 user@server \
|
||||
-t 'docker compose -f /srv/app/compose.yaml exec db psql -U postgres'
|
||||
```
|
||||
|
||||
Audit what is really bound before and after every deploy:
|
||||
|
||||
```bash
|
||||
docker compose config | grep -B8 "published:" # declared
|
||||
docker compose ps --format "{{.Service}}\t{{.Ports}}" # actual
|
||||
ss -tlnp | grep -v 127.0.0.1 # what the host exposes
|
||||
```
|
||||
|
||||
## Database
|
||||
|
||||
The template ships none by design. When `/architecture` picks one:
|
||||
|
||||
1. Add it as a second service in `compose.yaml` — no published port, reachable only
|
||||
over the internal Compose network
|
||||
1. Add it as a second service in `compose.yaml` — **no `ports:` entry**, reachable
|
||||
only over the internal Compose network. Use `expose:` if you want it documented.
|
||||
2. Add a named volume for its data
|
||||
3. Add `DATABASE_URL` to `.env.example`
|
||||
3. Add `DATABASE_URL` to `.env.example` (pointing at the service name, e.g. `db:5432`)
|
||||
4. Run migrations **before** the new container serves traffic. Either add a
|
||||
short-lived `migrate` service that the app depends on, or add the migration
|
||||
command to `scripts/update.sh` between build and restart.
|
||||
|
||||
Reference in New Issue
Block a user