From 9ac9ba8184a698a2c2a3b3d2f3f23cc31feb4119 Mon Sep 17 00:00:00 2001 From: Xiaoxia AI Date: Mon, 22 Jun 2026 10:02:03 +0800 Subject: [PATCH] fix(deploy): isolate production web api proxy --- infra/docker/compose.yml | 2 ++ infra/docker/deploy-production.sh | 1 + infra/docker/nginx-production.conf | 28 ++++++++++++++++++++++++++++ infra/docker/web-artifact.Dockerfile | 3 ++- tests/unit/test_release_scripts.py | 11 +++++++++++ 5 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 infra/docker/nginx-production.conf diff --git a/infra/docker/compose.yml b/infra/docker/compose.yml index c5afb4b1b..7e7451dc2 100644 --- a/infra/docker/compose.yml +++ b/infra/docker/compose.yml @@ -50,6 +50,8 @@ services: build: context: ../.. dockerfile: ${WEB_DOCKERFILE:-infra/docker/web.Dockerfile} + args: + NGINX_CONF: ${WEB_NGINX_CONF:-infra/docker/nginx.conf} container_name: xiaoxia-web-${ENV:-staging} restart: unless-stopped ports: diff --git a/infra/docker/deploy-production.sh b/infra/docker/deploy-production.sh index 15590a53f..58d0b63ca 100755 --- a/infra/docker/deploy-production.sh +++ b/infra/docker/deploy-production.sh @@ -44,6 +44,7 @@ export DOCKER_BUILDKIT=0 export COMPOSE_DOCKER_CLI_BUILD=0 export COMPOSE_PROJECT_NAME=xiaoxia-production-app export WEB_DOCKERFILE=infra/docker/web-artifact.Dockerfile +export WEB_NGINX_CONF=infra/docker/nginx-production.conf docker compose --env-file "$ENV_FILE" build --pull=false api docker compose --env-file "$ENV_FILE" build --pull=false worker diff --git a/infra/docker/nginx-production.conf b/infra/docker/nginx-production.conf new file mode 100644 index 000000000..c3300e053 --- /dev/null +++ b/infra/docker/nginx-production.conf @@ -0,0 +1,28 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss; + + location /api/ { + proxy_pass http://xiaoxia-api-production:8000/api/; + 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; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/infra/docker/web-artifact.Dockerfile b/infra/docker/web-artifact.Dockerfile index 3abb20b8e..1ce7e4524 100644 --- a/infra/docker/web-artifact.Dockerfile +++ b/infra/docker/web-artifact.Dockerfile @@ -1,6 +1,7 @@ FROM docker.m.daocloud.io/library/nginx:alpine AS runner +ARG NGINX_CONF=infra/docker/nginx.conf WORKDIR /usr/share/nginx/html COPY apps/web/dist ./ -COPY infra/docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY ${NGINX_CONF} /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/tests/unit/test_release_scripts.py b/tests/unit/test_release_scripts.py index 78ed3ca51..56f080348 100644 --- a/tests/unit/test_release_scripts.py +++ b/tests/unit/test_release_scripts.py @@ -26,6 +26,7 @@ def test_deploy_production_uses_production_infra_and_project(): assert "apps/web/dist/index.html" in script assert "Production deploy must not build frontend assets on the server" in script assert "WEB_DOCKERFILE=infra/docker/web-artifact.Dockerfile" in script + assert "WEB_NGINX_CONF=infra/docker/nginx-production.conf" in script assert "xiaoxia-postgres-production" in script assert "xiaoxia-redis-production" in script assert "xiaoxia-postgres\n" not in script @@ -79,12 +80,22 @@ def test_deploy_scripts_build_web_image_explicitly(): assert "docker compose build --pull=false web" in staging_script assert "docker compose --env-file \"$ENV_FILE\" build --pull=false web" in production_script assert "dockerfile: ${WEB_DOCKERFILE:-infra/docker/web.Dockerfile}" in compose + assert "NGINX_CONF: ${WEB_NGINX_CONF:-infra/docker/nginx.conf}" in compose + + +def test_production_nginx_proxies_to_production_api_container(): + config = Path("infra/docker/nginx-production.conf").read_text(encoding="utf-8") + + assert "proxy_pass http://xiaoxia-api-production:8000/api/;" in config + assert "proxy_pass http://api:8000/api/;" not in config def test_web_artifact_dockerfile_does_not_build_frontend_on_server(): dockerfile = Path("infra/docker/web-artifact.Dockerfile").read_text(encoding="utf-8") assert "COPY apps/web/dist ./" in dockerfile + assert "ARG NGINX_CONF=infra/docker/nginx.conf" in dockerfile + assert "COPY ${NGINX_CONF} /etc/nginx/conf.d/default.conf" in dockerfile assert "npm" not in dockerfile assert "node" not in dockerfile.lower()