From 3a096bbc3737b08bb2d8226d7b2f8fcc6ed9bc09 Mon Sep 17 00:00:00 2001 From: Jeet Debnath Date: Mon, 16 Mar 2026 11:05:44 +0530 Subject: [PATCH] initial docker setup --- .dockerignore | 12 +++ Dockerfile | 34 ++++++++ backend/.dockerignore | 12 +++ backend/.env.example | 4 + backend/Dockerfile | 15 ++++ docker-compose.yml | 58 +++++++++++++ docs/DOCKER_SETUP.md | 191 ++++++++++++++++++++++++++++++++++++++++++ nginx/default.conf | 29 +++++++ 8 files changed, 355 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 docker-compose.yml create mode 100644 docs/DOCKER_SETUP.md create mode 100644 nginx/default.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d10b530 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +dist +dist-ssr +.git +.gitignore +Dockerfile +docker-compose.yml +backend +*.log +.env +.env.* +coverage \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..53245e1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM node:20-alpine AS build + +WORKDIR /app + +ARG VITE_FIREBASE_API_KEY +ARG VITE_FIREBASE_AUTH_DOMAIN +ARG VITE_FIREBASE_PROJECT_ID +ARG VITE_FIREBASE_STORAGE_BUCKET +ARG VITE_FIREBASE_MESSAGING_SENDER_ID +ARG VITE_FIREBASE_APP_ID +ARG VITE_API_URL=/api + +ENV VITE_FIREBASE_API_KEY=${VITE_FIREBASE_API_KEY} +ENV VITE_FIREBASE_AUTH_DOMAIN=${VITE_FIREBASE_AUTH_DOMAIN} +ENV VITE_FIREBASE_PROJECT_ID=${VITE_FIREBASE_PROJECT_ID} +ENV VITE_FIREBASE_STORAGE_BUCKET=${VITE_FIREBASE_STORAGE_BUCKET} +ENV VITE_FIREBASE_MESSAGING_SENDER_ID=${VITE_FIREBASE_MESSAGING_SENDER_ID} +ENV VITE_FIREBASE_APP_ID=${VITE_FIREBASE_APP_ID} +ENV VITE_API_URL=${VITE_API_URL} + +COPY package.json package-lock.json* ./ +RUN npm install + +COPY . . +RUN npm run build + +FROM nginx:1.27-alpine AS runtime + +COPY nginx/default.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..ef904d8 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,12 @@ +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +.pytest_cache +.mypy_cache +.ruff_cache +.venv +venv +.env +*.log \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example index d9f6f1d..76a7b70 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -4,3 +4,7 @@ API_PORT=8001 ENVIRONMENT=development FRONTEND_URL=http://localhost:8000 +# Docker Compose values: +# MONGODB_URI=mongodb://mongo:27017 +# ENVIRONMENT=production + diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..b30cb2d --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-slim + +WORKDIR /app + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8001 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..759bc10 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,58 @@ +services: + frontend: + build: + context: . + dockerfile: Dockerfile + args: + VITE_FIREBASE_API_KEY: ${VITE_FIREBASE_API_KEY} + VITE_FIREBASE_AUTH_DOMAIN: ${VITE_FIREBASE_AUTH_DOMAIN} + VITE_FIREBASE_PROJECT_ID: ${VITE_FIREBASE_PROJECT_ID} + VITE_FIREBASE_STORAGE_BUCKET: ${VITE_FIREBASE_STORAGE_BUCKET} + VITE_FIREBASE_MESSAGING_SENDER_ID: ${VITE_FIREBASE_MESSAGING_SENDER_ID} + VITE_FIREBASE_APP_ID: ${VITE_FIREBASE_APP_ID} + VITE_API_URL: ${VITE_API_URL:-/api} + depends_on: + backend: + condition: service_started + ports: + - "127.0.0.1:8000:80" + restart: unless-stopped + networks: + - app_net + + backend: + build: + context: ./backend + dockerfile: Dockerfile + env_file: + - ./backend/.env + expose: + - "8001" + depends_on: + mongo: + condition: service_healthy + restart: unless-stopped + networks: + - app_net + + mongo: + image: mongo:6 + command: ["mongod", "--bind_ip", "0.0.0.0"] + volumes: + - mongo_data:/data/db + healthcheck: + test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + restart: unless-stopped + networks: + - app_net + +volumes: + mongo_data: + +networks: + app_net: + driver: bridge \ No newline at end of file diff --git a/docs/DOCKER_SETUP.md b/docs/DOCKER_SETUP.md new file mode 100644 index 0000000..a79c613 --- /dev/null +++ b/docs/DOCKER_SETUP.md @@ -0,0 +1,191 @@ +# Docker Setup Guide for Grateful Journal + +## Goal + +This Docker setup runs the full app locally with three containers: + +- Frontend (React app served by nginx) +- Backend (FastAPI) +- MongoDB + +The setup is intentionally private to the local machine: + +- Frontend is available only at `http://127.0.0.1:8000` +- Backend is not published to the host +- MongoDB is not published to the host +- Backend and MongoDB are reachable only from other containers in the same Docker Compose network + +This means other devices on the same network cannot access the UI, backend, or database. + +## Files Added for Docker + +- Root `Dockerfile` for the frontend build and nginx runtime +- `backend/Dockerfile` for FastAPI +- `docker-compose.yml` for orchestration +- `nginx/default.conf` for SPA serving and API proxying +- Root `.env` for frontend build variables +- `backend/.env` for backend runtime variables + +## Prerequisites + +- Docker Desktop installed and running +- Docker Compose available via `docker compose` + +## Environment Files + +### Frontend + +The root `.env` file is used during the frontend image build. + +Current values: + +```env +VITE_FIREBASE_API_KEY=... +VITE_FIREBASE_AUTH_DOMAIN=react-test-8cb04.firebaseapp.com +VITE_FIREBASE_PROJECT_ID=react-test-8cb04 +VITE_FIREBASE_STORAGE_BUCKET=react-test-8cb04.firebasestorage.app +VITE_FIREBASE_MESSAGING_SENDER_ID=1036594341832 +VITE_FIREBASE_APP_ID=1:1036594341832:web:9db6fa337e9cd2e953c2fd +VITE_API_URL=/api +``` + +`VITE_API_URL=/api` is important because nginx proxies `/api` requests to the backend container internally. + +### Backend + +The `backend/.env` file is loaded by the backend container at runtime. + +Current values: + +```env +MONGODB_URI=mongodb://mongo:27017 +MONGODB_DB_NAME=grateful_journal +API_PORT=8001 +ENVIRONMENT=production +FRONTEND_URL=http://localhost:8000 +``` + +`MONGODB_URI=mongodb://mongo:27017` works because Docker Compose gives the MongoDB service the hostname `mongo` on the internal network. + +## Network Model + +### Frontend + +The frontend service is published with: + +```yaml +ports: + - "127.0.0.1:8000:80" +``` + +This binds the container to localhost only. The app is reachable from your machine, but not from another device on your LAN. + +### Backend + +The backend uses: + +```yaml +expose: + - "8001" +``` + +`expose` makes port 8001 available to other containers, but not to your host machine or network. + +### MongoDB + +MongoDB has no `ports` section, so it is not reachable from outside Docker. Only the backend can talk to it over the Compose network. + +## Start the Stack + +From the project root: + +```bash +docker compose up --build +``` + +Then open: + +- Frontend: `http://127.0.0.1:8000` + +The backend API and MongoDB stay internal. + +## Stop the Stack + +```bash +docker compose down +``` + +To also remove the database volume: + +```bash +docker compose down -v +``` + +## Rebuild After Changes + +If you change frontend code, backend code, or environment variables: + +```bash +docker compose up --build +``` + +If you want a full rebuild without cache: + +```bash +docker compose build --no-cache +docker compose up +``` + +## Data Persistence + +MongoDB data is stored in the named Docker volume `mongo_data`. + +That means: + +- Restarting containers keeps the data +- Removing the containers keeps the data +- Running `docker compose down -v` removes the data + +## API Flow + +Browser requests follow this path: + +1. Browser loads the frontend from nginx on `127.0.0.1:8000` +2. Frontend sends API requests to `/api` +3. nginx forwards `/api` to `http://backend:8001/api/` +4. Backend connects to MongoDB at `mongodb://mongo:27017` + +This avoids exposing the backend directly to the host. + +## Firebase Note + +The frontend still requires the Firebase JavaScript SDK because login happens in the browser. + +The backend does not currently verify Firebase ID tokens, so `firebase-admin` is not part of this Docker setup. + +If backend token verification is added later, that would be a separate change. + +## Troubleshooting + +### Docker command not found + +Install Docker Desktop and confirm this works: + +```bash +docker --version +docker compose version +``` + +### Frontend loads but API calls fail + +Check that: + +- `backend/.env` contains `MONGODB_URI=mongodb://mongo:27017` +- Root `.env` contains `VITE_API_URL=/api` +- All containers are healthy with `docker compose ps` + +### Want to inspect MongoDB from the host + +This setup does not expose MongoDB intentionally. + +If you want host access temporarily for debugging, add a port mapping to the MongoDB service, but that weakens the local-only isolation model. diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..62d319e --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,29 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location /api/ { + proxy_pass http://backend:8001/api/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /health { + proxy_pass http://backend:8001/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file