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:
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user