Align all app containers to UID 1001, add infra protocol, update README

- frontend prod: USER root for adduser, then USER appuser (1001:1001); fixes
  build failure caused by nginx-unprivileged already setting USER nginx
- docker-compose: frontend user updated to 1001:1001 (was 101:101)
- CLAUDE.md: add infrastructure change protocol (update README + test both
  stacks after any Dockerfile/compose/nginx change); fix stale passlib ref
- README: container table shows nginx-unprivileged image, UID column, internal
  port 8080 note; Current State notes all containers run as non-root

Both dev and prod stacks tested and verified (health, login, /users/me,
frontend serving, all containers confirmed non-root via docker inspect).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-04-13 17:29:02 +02:00
parent a5baef73d9
commit e117a33a73
5 changed files with 49 additions and 9 deletions
+16 -1
View File
@@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
| Layer | Tech |
|---|---|
| Backend | FastAPI (async), SQLAlchemy 2 (async), Alembic, PostgreSQL |
| Auth | JWT via `python-jose`, bcrypt via `passlib` |
| Auth | JWT via `python-jose`, bcrypt via `bcrypt` (direct, no passlib) |
| Frontend | React 18, TypeScript, Vite, React Router v6, TanStack Query, Axios |
| Dev DB | PostgreSQL 16 via Docker Compose |
@@ -92,6 +92,21 @@ Browser → Vite dev server (:5173)
Always run `git push` immediately after every `git commit`.
## Infrastructure change protocol
After **any** change to Dockerfiles, `docker-compose*.yml`, `nginx.conf`, setup scripts, or installation / usage procedures:
1. **Update `README.md`** — keep the Containers table, ports, image names, and Current State section accurate.
2. **Spin up the dev stack** and verify that login and registration work end-to-end:
```bash
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
```
3. **Spin up the prod stack** and run the same checks:
```bash
docker compose up --build -d
```
4. Confirm each container is running as a non-root user (`docker inspect <container> --format '{{.Config.User}}'`).
## Security hook
A pre-commit hook lives in `.githooks/pre-commit` and runs `scripts/security_check.py` inside a Docker container. It is registered via `git config core.hooksPath .githooks` (already set in this repo).
+6 -5
View File
@@ -16,17 +16,18 @@ A fullstack SaaS web application built with FastAPI, React, and PostgreSQL.
- Protected dashboard route
- `/api/users/me` — authenticated user info
- 3 separate Docker containers: `db` (PostgreSQL), `backend` (FastAPI), `frontend` (nginx)
- All containers run as non-root users (UID 1001 for backend and frontend, UID 70 for db)
- Dev environment seeds a test user automatically on startup (`test@example.com` / `Test123!`)
- Password policy: min 8 chars, upper + lowercase, digit, special character, no common words
- Pre-commit security hook (`scripts/security_check.py`) runs inside Docker on every commit
## Containers
| Container | Image | Port | Description |
|---|---|---|---|
| `db` | postgres:16-alpine | 5432 | PostgreSQL database |
| `backend` | custom (python:3.12-slim) | 8000 | FastAPI management API |
| `frontend` | custom (nginx:alpine) | 80 | React UI served by nginx |
| Container | Image | Port | User (UID:GID) | Description |
|---|---|---|---|---|
| `db` | postgres:16-alpine | 5432 | 70:70 (postgres) | PostgreSQL database |
| `backend` | custom (python:3.12-slim) | 8000 | 1001:1001 (appuser) | FastAPI management API |
| `frontend` | custom (nginxinc/nginx-unprivileged:alpine) | 80 | 1001:1001 (appuser) | React UI served by nginx (internal port 8080) |
The frontend nginx container proxies `/api/*` to the backend container internally — no CORS headers needed in production.
+18 -1
View File
@@ -15,7 +15,24 @@ All containers now run as non-root users with explicit UID:GID assignments enfor
| `frontend` (prod) | `nginx` | `101:101` | Switched to `nginxinc/nginx-unprivileged:alpine`; listens on 8080 |
| `frontend` (dev) | `appuser` | `1001:1001` | Created via `adduser` in builder stage |
## Files Modified
# 2026-04-13 — Frontend prod UID 1001, infra change protocol, README update
**Timestamp:** 2026-04-13T01:00:00
## Summary
Aligned frontend prod container to UID 1001 (same as all other app containers), added infrastructure change protocol to CLAUDE.md, updated README with container table and rootless note. Both dev and prod stacks verified working.
## Files Modified (this entry)
- `frontend/Dockerfile` — prod stage: added `USER root` + `addgroup`/`adduser` for appuser 1001:1001, `USER appuser`; removed stale 101 reference
- `docker-compose.yml` — frontend `user:` updated from `"101:101"` to `"1001:1001"`
- `CLAUDE.md` — added Infrastructure change protocol section; fixed stale passlib reference in stack table
- `README.md` — updated container table with `nginxinc/nginx-unprivileged:alpine`, UID columns, internal port note; added rootless note to Current State
---
## Files Modified (previous entry)
- `backend/Dockerfile` — added `groupadd`/`useradd` for appuser (1001:1001), `--chown` on all `COPY` directives, `USER appuser`
- `frontend/Dockerfile` — builder stage: added `addgroup`/`adduser` for appuser (1001:1001), `USER appuser`; prod stage: switched to `nginxinc/nginx-unprivileged:alpine`, `EXPOSE 8080`
+1 -1
View File
@@ -42,7 +42,7 @@ services:
context: ./frontend
dockerfile: Dockerfile
network: host
user: "101:101" # nginx user UID:GID in nginx-unprivileged:alpine
user: "1001:1001"
restart: unless-stopped
ports:
- "80:8080"
+8 -1
View File
@@ -15,9 +15,16 @@ RUN npm ci
COPY --chown=appuser:appuser . .
RUN npm run build
# ── Stage 2: serve with nginx (unprivileged, UID 101) ────────────────────────
# ── Stage 2: serve with nginx (unprivileged, UID 1001) ────────────────────────
FROM nginxinc/nginx-unprivileged:alpine
# nginx-unprivileged already sets USER nginx (101). Step up to root only for
# user creation, then drop back. All nginx writable paths go through /tmp
# (world-writable) so appuser can run the server without extra chowns.
USER root
RUN addgroup -g 1001 appuser && adduser -u 1001 -G appuser -D appuser
USER appuser
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf