diff --git a/README.md b/README.md index 22ef0e1..7e02a98 100644 --- a/README.md +++ b/README.md @@ -15,35 +15,51 @@ A fullstack SaaS web application built with FastAPI, React, and PostgreSQL. - User registration and login (JWT auth) - Protected dashboard route - `/api/users/me` — authenticated user info -- Vite dev server proxies `/api` to FastAPI — no CORS issues in development +- 3 separate Docker containers: `db` (PostgreSQL), `backend` (FastAPI), `frontend` (nginx) + +## 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 | + +The frontend nginx container proxies `/api/*` to the backend container internally — no CORS headers needed in production. ## Installation ### Prerequisites -- Python 3.11+ -- Node.js 18+ -- PostgreSQL 16 (or Docker) +- Docker + Docker Compose -### Option A — Docker (recommended) +### Production ```bash git clone cd destroying_sap -cp .env.example backend/.env -docker compose up --build +cp .env.example backend/.env # edit SECRET_KEY at minimum +docker compose up --build -d ``` -- Frontend: http://localhost:5173 -- Backend / API docs: http://localhost:8000/docs +- Frontend: http://localhost +- API docs: http://localhost:8000/docs -### Option B — Local +### Development (hot reload) + +```bash +docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build +``` + +- Frontend (Vite): http://localhost:5173 +- Backend (uvicorn --reload): http://localhost:8000 + +### Local (no Docker) **1. Start PostgreSQL** ```bash docker compose up db -d -# or use a local PostgreSQL instance and update backend/.env ``` **2. Backend** @@ -52,7 +68,7 @@ docker compose up db -d cd backend python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -e ".[dev]" -cp ../.env.example .env # edit DATABASE_URL and SECRET_KEY as needed +cp ../.env.example .env alembic upgrade head uvicorn app.main:app --reload ``` @@ -60,9 +76,7 @@ uvicorn app.main:app --reload **3. Frontend** ```bash -cd frontend -npm install -npm run dev +cd frontend && npm install && npm run dev ``` ## Environment Variables diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..e08b72c --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,26 @@ +# ── Stage 1: dependency installation ───────────────────────────────────────── +FROM python:3.12-slim AS builder + +WORKDIR /app + +RUN pip install --upgrade pip + +COPY pyproject.toml . +RUN pip install --prefix=/install . + +# ── Stage 2: runtime ────────────────────────────────────────────────────────── +FROM python:3.12-slim + +WORKDIR /app + +# Copy installed packages from builder +COPY --from=builder /install /usr/local + +# Copy application source +COPY app ./app +COPY alembic ./alembic +COPY alembic.ini . + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/changelog/2026-04-12_dockerize.md b/changelog/2026-04-12_dockerize.md new file mode 100644 index 0000000..abeb2b8 --- /dev/null +++ b/changelog/2026-04-12_dockerize.md @@ -0,0 +1,19 @@ +# 2026-04-12 — Dockerize all containers + +**Timestamp:** 2026-04-12T15:00:00 + +## Summary + +Added proper Dockerfiles for backend and frontend. Split compose setup into production (`docker-compose.yml`) and development (`docker-compose.dev.yml`) modes. Updated README to reflect new container architecture. + +## Files Added + +- `backend/Dockerfile` — multi-stage build: pip install in builder stage, minimal python:3.12-slim runtime; runs uvicorn in production mode +- `frontend/Dockerfile` — multi-stage build: Node 20 Alpine builds the Vite bundle, nginx:alpine serves the static files +- `frontend/nginx.conf` — nginx config for SPA fallback routing and `/api/` reverse proxy to the backend container +- `docker-compose.dev.yml` — development overrides: mounts source for hot reload, uses Vite dev server instead of nginx + +## Files Modified + +- `docker-compose.yml` — rewritten: proper `build.context` + `dockerfile` references, health check on db, `depends_on` with `condition: service_healthy`, env var interpolation via `${VAR:-default}`, frontend now served on port 80 via nginx +- `README.md` — updated Current State, added Containers table, replaced install options with Production / Development / Local sections diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..7b41933 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,20 @@ +# Development overrides — hot reload for backend and frontend +# Usage: docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build + +services: + + backend: + command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + volumes: + - ./backend:/app + + frontend: + build: + context: ./frontend + target: builder # stop at the Node stage, skip nginx + command: npm run dev -- --host 0.0.0.0 + ports: + - "5173:5173" + volumes: + - ./frontend:/app + - /app/node_modules diff --git a/docker-compose.yml b/docker-compose.yml index 981e1c7..3533fa7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,35 +1,46 @@ services: + + # ── Database ──────────────────────────────────────────────────────────────── db: - image: postgres:16 + image: postgres:16-alpine + restart: unless-stopped environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: password - POSTGRES_DB: destroying_sap + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} + POSTGRES_DB: ${POSTGRES_DB:-destroying_sap} ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"] + interval: 5s + timeout: 5s + retries: 10 + # ── Backend (management) ──────────────────────────────────────────────────── backend: - build: ./backend - command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload - volumes: - - ./backend:/app + build: + context: ./backend + dockerfile: Dockerfile + restart: unless-stopped + env_file: ./backend/.env + environment: + DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-destroying_sap} ports: - "8000:8000" - environment: - DATABASE_URL: postgresql+asyncpg://postgres:password@db:5432/destroying_sap depends_on: - - db + db: + condition: service_healthy + # ── Frontend (UI) ──────────────────────────────────────────────────────────── frontend: - build: ./frontend - command: npm run dev -- --host - volumes: - - ./frontend:/app - - /app/node_modules + build: + context: ./frontend + dockerfile: Dockerfile + restart: unless-stopped ports: - - "5173:5173" + - "80:80" depends_on: - backend diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..ef90a6a --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,18 @@ +# ── Stage 1: build ──────────────────────────────────────────────────────────── +FROM node:20-alpine AS builder + +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm ci + +COPY . . +RUN npm run build + +# ── Stage 2: serve with nginx ───────────────────────────────────────────────── +FROM nginx:alpine + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..efa582f --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,17 @@ +server { + listen 80; + root /usr/share/nginx/html; + index index.html; + + # Proxy API calls to the backend container + location /api/ { + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + # SPA fallback — all non-asset routes serve index.html + location / { + try_files $uri $uri/ /index.html; + } +}