From c10824388ccb072c4e8feaf78d783a417b76487b Mon Sep 17 00:00:00 2001 From: Fredrick Onyango Date: Sat, 6 Jun 2026 22:37:16 +0300 Subject: [PATCH] Add Nginx infra, docker-compose override, health endpoint and dockerignore --- .dockerignore | 14 ++++++++ app/main.py | 7 +++- docker-compose.nginx.yml | 27 +++++++++++++++ infra/nginx/conf.d/default.conf | 59 +++++++++++++++++++++++++++++++++ infra/nginx/nginx.conf | 26 +++++++++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 docker-compose.nginx.yml create mode 100644 infra/nginx/conf.d/default.conf create mode 100644 infra/nginx/nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6a1067f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +# Ignore build artifacts and env +__pycache__/ +*.py[cod] +.env +.env.* +redis_data/ +*.log + +# Docker +docker-compose.override.yml + +# Editor +.vscode/ +.idea/ diff --git a/app/main.py b/app/main.py index b801151..e1747e8 100644 --- a/app/main.py +++ b/app/main.py @@ -1,4 +1,5 @@ -from fastapi import FastAPI, Request, HTTPException +from fastapi import FastAPI, Request, Form, HTTPException +import asyncio import os from app.classifier import classify_urgency from app.convo_store import ConvoStore @@ -9,6 +10,10 @@ app = FastAPI() store = ConvoStore(redis_url=settings.REDIS_URL) +@app.get("/health") +async def health(): + return {"status": "ok"} + @app.post("/webhook") async def webhook(request: Request): # Twilio posts form-encoded data for incoming messages diff --git a/docker-compose.nginx.yml b/docker-compose.nginx.yml new file mode 100644 index 0000000..a50dd1f --- /dev/null +++ b/docker-compose.nginx.yml @@ -0,0 +1,27 @@ +version: '3.8' + +services: + nginx: + image: nginx:1.25-alpine + depends_on: + - app + ports: + - "80:80" + - "443:443" + volumes: + - ./infra/nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./infra/nginx/conf.d:/etc/nginx/conf.d:ro + - certbot-etc:/etc/letsencrypt + - certbot-var:/var/www/certbot + restart: unless-stopped + + certbot: + image: certbot/certbot + volumes: + - certbot-etc:/etc/letsencrypt + - certbot-var:/var/www/certbot + entrypoint: /bin/sh -c 'trap exit TERM; while :; do sleep 12h & wait $${!}; certbot renew; done' + +volumes: + certbot-etc: + certbot-var: diff --git a/infra/nginx/conf.d/default.conf b/infra/nginx/conf.d/default.conf new file mode 100644 index 0000000..3a82b6c --- /dev/null +++ b/infra/nginx/conf.d/default.conf @@ -0,0 +1,59 @@ +upstream app { + # Use the docker-compose service name or Kubernetes service name + server app:8000; +} + +server { + listen 80; + server_name _; + + # Allow ACME challenges (certbot) to use /.well-known/acme-challenge/ + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + # Health endpoint (transparent proxy to app health) + location = /health { + proxy_pass http://app/health; + 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_http_version 1.1; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_read_timeout 60s; + proxy_send_timeout 60s; + } + + # Main app (webhook + API) + location / { + proxy_pass http://app; + 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; + + # Forward original request headers (Twilio/Meta signature headers are needed) + proxy_set_header X-Original-URI $request_uri; + proxy_http_version 1.1; + proxy_set_header Connection ""; + + # Disable buffering for proxied responses (good for streaming or low-latency) + proxy_buffering off; + + # Timeouts (tune as needed) + proxy_read_timeout 120s; + proxy_send_timeout 120s; + + # Max body size (increase if you accept large media) + client_max_body_size 8M; + + # Proxy buffers (tune for throughput) + proxy_buffers 8 16k; + proxy_buffer_size 32k; + } + + # Security headers for TLS (set only when TLS is enabled) + # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; +} diff --git a/infra/nginx/nginx.conf b/infra/nginx/nginx.conf new file mode 100644 index 0000000..3fb4a8f --- /dev/null +++ b/infra/nginx/nginx.conf @@ -0,0 +1,26 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + sendfile on; + tcp_nopush on; + keepalive_timeout 65; + server_tokens off; # hide nginx version in responses + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log warn; + + gzip on; + gzip_types text/plain application/json application/javascript text/css application/xml; + + include /etc/nginx/conf.d/*.conf; +}