From fee57e154b9837b439b7c555e240cfe050753a38 Mon Sep 17 00:00:00 2001 From: Tara Menon Date: Sun, 20 Sep 2026 11:22:20 +0530 Subject: [PATCH] =?UTF-8?q?prod:=20hardened=20deploy=20config=20for=20srv1?= =?UTF-8?q?289857=20=E2=80=94=20prod=20container=20names/ports=20(frontend?= =?UTF-8?q?=20:3882,=20backend=20:8010,=20db=20:5440),=20Dockerfile.prod?= =?UTF-8?q?=20(uvicorn=20--workers=201,=20init=5Fdb=20race=20fix),=20next.?= =?UTF-8?q?config.ts=20BACKEND=5FINTERNAL=5FURL=20build-arg=20rewrite,=20c?= =?UTF-8?q?elery=20worker/beat=20healthchecks=20(no-HTTP=20daemons:=20insp?= =?UTF-8?q?ect=20ping=20w/=20tmp-file=20output,=20/proc=20cmdline=20grep;?= =?UTF-8?q?=20pgrep=20unavailable=20in=20image),=20pgbouncer=20removed,=20?= =?UTF-8?q?restart:=20unless-stopped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 5 +- backend/Dockerfile.prod | 21 ++++ docker-compose.yml | 249 +++++++++++++++++++++------------------- next.config.ts | 11 +- 4 files changed, 166 insertions(+), 120 deletions(-) create mode 100644 backend/Dockerfile.prod diff --git a/Dockerfile b/Dockerfile index 5e5f55b..503d8fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,10 +18,13 @@ COPY . . # Set environment variables for build ENV NEXT_TELEMETRY_DISABLED=1 - # Accept API URL as build arg — defaults to Docker-internal service name ARG NEXT_PUBLIC_API_URL=http://backend:8000 ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} +# BACKEND_INTERNAL_URL drives the server-side /api rewrite; must be a build-time +# arg so it is compiled into next.config.ts (which is evaluated at build time). +ARG BACKEND_INTERNAL_URL +ENV BACKEND_INTERNAL_URL=$BACKEND_INTERNAL_URL RUN npm run build diff --git a/backend/Dockerfile.prod b/backend/Dockerfile.prod new file mode 100644 index 0000000..d1bb755 --- /dev/null +++ b/backend/Dockerfile.prod @@ -0,0 +1,21 @@ +FROM python:3.11-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + build-essential \ + libpq-dev \ + curl \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8000 + +HEALTHCHECK --interval=10s --timeout=5s --retries=12 \ + CMD curl -f http://localhost:8000/health || exit 1 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"] diff --git a/docker-compose.yml b/docker-compose.yml index ab03d5c..cf206aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,13 +3,14 @@ version: '3.8' services: postgres: image: postgres:15-alpine - container_name: pragativeer_db + container_name: pragativeer_prod_db + restart: unless-stopped environment: POSTGRES_USER: pragativeer_admin POSTGRES_PASSWORD: local_secure_password_2026 POSTGRES_DB: pragativeer_prod ports: - - "5432:5432" + - "5440:5432" volumes: - pgdata:/var/lib/postgresql/data networks: @@ -18,131 +19,29 @@ services: test: ["CMD-SHELL", "pg_isready -U pragativeer_admin -d pragativeer_prod"] interval: 5s timeout: 5s - retries: 5 - - pgbouncer: - image: edoburu/pgbouncer - container_name: pragativeer_pgbouncer - environment: - - DATABASE_URL=postgres://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod - volumes: - - ./pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro - - ./userlist.txt:/etc/pgbouncer/userlist.txt:ro - depends_on: - postgres: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 || exit 0"] - interval: 5s - timeout: 5s - retries: 5 - networks: - - pragativeer_network + retries: 12 redis: image: redis:7-alpine - container_name: pragativeer_cache - ports: - - "6379:6379" + container_name: pragativeer_prod_cache + restart: unless-stopped volumes: - redisdata:/data networks: - pragativeer_network - - - backend: - build: - context: ./backend - dockerfile: Dockerfile.dev - container_name: pragativeer_backend - volumes: - - ./backend:/app - - ./admin_uploads:/app/admin_uploads:ro - ports: - - "8000:8000" - environment: - - DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@pgbouncer:5432/pragativeer_prod - - REDIS_URL=redis://redis:6379/0 - - ENV=development - - MINIO_ENDPOINT=minio:9000 - - MINIO_ACCESS_KEY=minio_admin - - MINIO_SECRET_KEY=minio_secure_password_2026 - - MINIO_BUCKET=pragativeer-uploads - env_file: - - ./backend/.env - depends_on: - postgres: - condition: service_healthy - pgbouncer: - condition: service_healthy - networks: - - pragativeer_network - - worker: - build: - context: ./backend - dockerfile: Dockerfile.dev - container_name: pragativeer_worker - command: celery -A worker.celery_app worker --loglevel=info - volumes: - - ./backend:/app - environment: - - DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@pgbouncer:5432/pragativeer_prod - - REDIS_URL=redis://redis:6379/0 - - MINIO_ENDPOINT=minio:9000 - - MINIO_ACCESS_KEY=minio_admin - - MINIO_SECRET_KEY=minio_secure_password_2026 - - MINIO_BUCKET=pragativeer-uploads - env_file: - - ./backend/.env - depends_on: - - postgres - - redis - networks: - - pragativeer_network - - beat: - build: - context: ./backend - dockerfile: Dockerfile.dev - container_name: pragativeer_beat - command: celery -A worker.celery_app beat --loglevel=info - volumes: - - ./backend:/app - environment: - - DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@pgbouncer:5432/pragativeer_prod - - REDIS_URL=redis://redis:6379/0 - env_file: - - ./backend/.env - depends_on: - - postgres - - redis - networks: - - pragativeer_network - - frontend: - build: - context: . - dockerfile: Dockerfile - args: - - NEXT_PUBLIC_API_URL=http://backend:8000 - container_name: pragativeer_frontend - ports: - - "3000:3000" - environment: - - NEXT_PUBLIC_API_URL=http://backend:8000 - - NEXT_PUBLIC_PAYMENT_GATEWAY=razorpay - depends_on: - - backend - networks: - - pragativeer_network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 12 minio: image: minio/minio:latest - container_name: pragativeer_storage + container_name: pragativeer_prod_storage + restart: unless-stopped ports: - - "9000:9000" - - "9001:9001" + - "9025:9000" + - "9026:9001" volumes: - miniodata:/data environment: @@ -151,6 +50,124 @@ services: command: server /data --console-address ":9001" networks: - pragativeer_network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 5s + timeout: 5s + retries: 12 + + backend: + build: + context: ./backend + dockerfile: Dockerfile.prod + container_name: pragativeer_prod_backend + restart: unless-stopped + volumes: + - ./admin_uploads:/app/admin_uploads:ro + ports: + - "8010:8000" + environment: + - DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod + - REDIS_URL=redis://redis:6379/0 + - ENV=production + - MINIO_ENDPOINT=minio:9000 + - MINIO_ACCESS_KEY=minio_admin + - MINIO_SECRET_KEY=minio_secure_password_2026 + - MINIO_BUCKET=pragativeer-uploads + - TZ=Asia/Kolkata + env_file: + - ./backend/.env + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + minio: + condition: service_healthy + networks: + - pragativeer_network + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"] + interval: 10s + timeout: 5s + retries: 12 + + worker: + build: + context: ./backend + dockerfile: Dockerfile.prod + container_name: pragativeer_prod_worker + restart: unless-stopped + command: celery -A worker.celery_app worker --loglevel=info + environment: + - DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod + - REDIS_URL=redis://redis:6379/0 + - MINIO_ENDPOINT=minio:9000 + - MINIO_ACCESS_KEY=minio_admin + - MINIO_SECRET_KEY=minio_secure_password_2026 + - MINIO_BUCKET=pragativeer-uploads + env_file: + - ./backend/.env + depends_on: + - postgres + - redis + networks: + - pragativeer_network + healthcheck: + # Celery worker serves no HTTP. Verify liveness via celery inspect ping. + # NOTE: output goes to a tmp file (not a pipe) — celery's forked children hold + # the pipe open and hang the check. Verified working in-container. + test: ["CMD-SHELL", "celery -A worker.celery_app inspect ping -d celery@$$HOSTNAME --timeout=5 > /tmp/hc.txt 2>&1; grep -q pong /tmp/hc.txt || exit 1"] + # Python cold-start makes the ping take 15-20s under load — generous timeout. + interval: 60s + timeout: 40s + retries: 3 + + beat: + build: + context: ./backend + dockerfile: Dockerfile.prod + container_name: pragativeer_prod_beat + restart: unless-stopped + command: celery -A worker.celery_app beat --loglevel=info + environment: + - DATABASE_URL=postgresql://pragativeer_admin:local_secure_password_2026@postgres:5432/pragativeer_prod + - REDIS_URL=redis://redis:6379/0 + env_file: + - ./backend/.env + depends_on: + - postgres + - redis + networks: + - pragativeer_network + healthcheck: + # Celery beat is a scheduler — no HTTP endpoint. pgrep isn't installed in the + # image; use a /proc scan instead. Verified working in-container. + test: ["CMD-SHELL", "grep -as 'celery.*[b]eat' /proc/*/cmdline > /dev/null 2>&1 || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + + frontend: + build: + context: . + dockerfile: Dockerfile + args: + - NEXT_PUBLIC_API_URL=https://pragativeer.com + - BACKEND_INTERNAL_URL=http://backend:8000 + container_name: pragativeer_prod_frontend + restart: unless-stopped + ports: + - "3882:3000" + environment: + - NODE_ENV=production + - BACKEND_INTERNAL_URL=http://backend:8000 + - NEXT_PUBLIC_API_URL=https://pragativeer.com + - NEXT_PUBLIC_PAYMENT_GATEWAY=razorpay + depends_on: + - backend + networks: + - pragativeer_network volumes: pgdata: diff --git a/next.config.ts b/next.config.ts index f96b171..34f5bf3 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,8 +1,13 @@ import type { NextConfig } from "next"; -// Use NEXT_PUBLIC_API_URL for local dev (http://localhost:8000), -// fall back to the Docker-internal service name for containerised deployments. -const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://backend:8000"; +// Backend URL used for the SERVER-SIDE /api rewrite. Must resolve on the +// Docker network, so it must NOT be a public origin (that would create a loop). +// Use BACKEND_INTERNAL_URL (set at container runtime) so client components can +// independently use NEXT_PUBLIC_API_URL (public origin, baked at build time). +const API_BASE = + process.env.BACKEND_INTERNAL_URL || + process.env.NEXT_PUBLIC_API_URL || + "http://backend:8000"; const nextConfig: NextConfig = { output: "standalone",