diff --git a/Dockerfile b/Dockerfile index 2112ab0..b9bdff0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,7 +50,7 @@ COPY . . RUN python3 -c "from kjvstudy_org.utils.search_index import init_search_index; init_search_index()" # Copy nginx config and startup script -COPY nginx.conf /etc/nginx/nginx.conf +COPY nginx-prod.conf /etc/nginx/nginx.conf COPY start.sh /app/start.sh RUN chmod +x /app/start.sh diff --git a/docker-compose.yml b/docker-compose.yml index e4d7b3f..73e77e9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,19 @@ services: - web: - build: . + nginx: + image: nginx:alpine ports: - "8000:8000" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + - ./kjvstudy_org/static:/app/kjvstudy_org/static:ro + depends_on: + - web + restart: unless-stopped + + web: + build: . + expose: + - "8001" volumes: - .:/app - /app/.venv @@ -11,4 +22,4 @@ services: - PRELOAD_INTERLINEAR=true - DISABLE_ANALYTICS=true restart: unless-stopped - command: uv run uvicorn kjvstudy_org.server:app --host 0.0.0.0 --port 8000 --reload --reload-include '*.json' + command: uv run uvicorn kjvstudy_org.server:app --host 0.0.0.0 --port 8001 --reload --reload-include '*.json' diff --git a/nginx-prod.conf b/nginx-prod.conf new file mode 100644 index 0000000..f4150c7 --- /dev/null +++ b/nginx-prod.conf @@ -0,0 +1,97 @@ +worker_processes auto; +error_log /dev/stderr warn; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + access_log /dev/stdout combined; + + # Request buffering - absorb slow clients + client_body_buffer_size 128k; + client_max_body_size 10m; + client_body_timeout 60s; + client_header_timeout 60s; + + # Response buffering + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 32k; + proxy_busy_buffers_size 64k; + + # Keepalive + keepalive_timeout 65; + keepalive_requests 1000; + + # Compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml; + + # Upstream - uvicorn (production single container) + upstream app { + server 127.0.0.1:8001; + keepalive 32; + } + + server { + listen 8000; + server_name _; + + # Static files - serve directly with caching + location /static/ { + alias /app/kjvstudy_org/static/; + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + # Service worker - special cache headers + location = /sw.js { + proxy_pass http://app; + 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; + add_header Cache-Control "no-cache"; + } + + # All other requests - proxy to uvicorn + location / { + proxy_pass http://app; + 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; + proxy_set_header Connection ""; + + # Request buffering + proxy_request_buffering on; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # Health check endpoint - bypass buffering for speed + location = /api/health { + proxy_pass http://app; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_buffering off; + } + } +} diff --git a/nginx.conf b/nginx.conf index c452cce..b746196 100644 --- a/nginx.conf +++ b/nginx.conf @@ -38,9 +38,9 @@ http { gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml; - # Upstream - uvicorn + # Upstream - uvicorn (use 'web' for docker-compose, '127.0.0.1' for single container) upstream app { - server 127.0.0.1:8001; + server web:8001; keepalive 32; }