From 80f8f752084074f591aa51a334bad2a0ef701f65 Mon Sep 17 00:00:00 2001 From: continuist Date: Sat, 20 Sep 2025 20:31:24 -0400 Subject: [PATCH] Try to connect up nginx in prod server --- .forgejo/workflows/ci.yml | 15 ++++ deploy/prod-pod.yml | 38 +++------- nginx/nginx.conf | 145 +++++++++----------------------------- 3 files changed, 58 insertions(+), 140 deletions(-) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 6799c68..117fb46 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -245,6 +245,21 @@ jobs: podman --remote pull "$REGISTRY_HOST/$APP_NAME/sharenet-backend-api-postgres:$IMAGE_TAG" podman --remote pull "$REGISTRY_HOST/$APP_NAME/sharenet-frontend:$IMAGE_TAG" + - name: Prepare in-pod nginx config on host + run: | + set -euo pipefail + # create dir on host (via user namespace) + podman --remote unshare mkdir -p /opt/sharenet/nginx /opt/sharenet/volumes/nginx-cache + # render temp config (inside the job container) + apk add --no-cache gettext >/dev/null + envsubst < nginx/nginx.conf > /tmp/nginx.conf + # write it onto the host + podman --remote unshare sh -c 'cat > /opt/sharenet/nginx/nginx.conf' < /tmp/nginx.conf + # reasonable perms for rootless mount + podman --remote unshare chown -R 1001:1001 /opt/sharenet + podman --remote unshare chmod 0755 /opt/sharenet /opt/sharenet/nginx /opt/sharenet/volumes /opt/sharenet/volumes/nginx-cache + podman --remote unshare chmod 0644 /opt/sharenet/nginx/nginx.conf || true + - name: Install envsubst (Alpine) run: apk add --no-cache gettext diff --git a/deploy/prod-pod.yml b/deploy/prod-pod.yml index a256651..ca4fc24 100644 --- a/deploy/prod-pod.yml +++ b/deploy/prod-pod.yml @@ -150,27 +150,19 @@ spec: capabilities: drop: ["ALL"] ports: - - containerPort: 80 - hostPort: 8080 - protocol: TCP - - containerPort: 443 - hostPort: 8443 - protocol: TCP + - containerPort: 8080 # inside pod + hostIP: 127.0.0.1 # only loopback on host + hostPort: 18080 # high port exposed to host + - containerPort: 8090 # health inside pod (not exposed) volumeMounts: - - { name: nginx-run, mountPath: /var/run, readOnly: false } - - { name: nginx-conf, mountPath: /etc/nginx/nginx.conf, readOnly: true, subPath: nginx.conf } - - { name: nginx-cache, mountPath: /var/cache/nginx, readOnly: false } - - { name: letsencrypt, mountPath: /etc/letsencrypt, readOnly: true } + - { name: nginx-run, mountPath: /var/run, readOnly: false } + - { name: nginx-cache, mountPath: /var/cache/nginx, readOnly: false } + - { name: nginx-conf, mountPath: /etc/nginx/nginx.conf, readOnly: true, subPath: nginx.conf } # Health check livenessProbe: - httpGet: - path: /healthz - port: 8090 - scheme: HTTP + httpGet: { path: /healthz, port: 8090, scheme: HTTP } initialDelaySeconds: 10 periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 3 # Resource limits resources: requests: @@ -193,15 +185,7 @@ spec: emptyDir: { medium: Memory } - name: nginx-run emptyDir: {} - - name: nginx-conf - hostPath: - path: /opt/sharenet/nginx - type: Directory - name: nginx-cache - hostPath: - path: /opt/sharenet/volumes/nginx-cache - type: DirectoryOrCreate - - name: letsencrypt - hostPath: - path: /etc/letsencrypt - type: Directory \ No newline at end of file + hostPath: { path: /opt/sharenet/volumes/nginx-cache, type: DirectoryOrCreate } + - name: nginx-conf + hostPath: { path: /opt/sharenet/nginx, type: Directory } \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf index c9bccdf..67fdd53 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,118 +1,37 @@ -events { - worker_connections 1024; -} +user nginx; +worker_processes auto; +pid /var/run/nginx.pid; +events { worker_connections 1024; } http { - upstream frontend { - server frontend:3000; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + # health + server { + listen 8090; + location = /healthz { return 200 "ok\n"; add_header Content-Type text/plain; } + } + + # public HTTP entrypoint (host will terminate TLS and proxy here) + server { + listen 8080; + + # frontend default + location / { + proxy_pass http://127.0.0.1:${PROD_FRONTEND_PORT}; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; } - upstream backend { - server backend:3001; + # backend API + location /api/ { + proxy_pass http://127.0.0.1:${PROD_BACKEND_PORT}/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; } - - # Rate limiting - limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; - limit_req_zone $binary_remote_addr zone=frontend:10m rate=30r/s; - - # Gzip compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_proxied any; - gzip_comp_level 6; - gzip_types - text/plain - text/css - text/xml - text/javascript - application/json - application/javascript - application/xml+rss - application/atom+xml - image/svg+xml; - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; - - server { - listen 80; - server_name _; - - # Redirect HTTP to HTTPS - return 301 https://$host$request_uri; - } - - server { - listen 443 ssl http2; - server_name _; - - # SSL configuration - # These paths match Let's Encrypt certificate files copied in the CI/CD setup guide - ssl_certificate /etc/nginx/ssl/fullchain.pem; - ssl_certificate_key /etc/nginx/ssl/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; - ssl_prefer_server_ciphers off; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - - # Frontend routes - location / { - limit_req zone=frontend burst=20 nodelay; - - proxy_pass http://frontend; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - 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_cache_bypass $http_upgrade; - } - - # API routes - location /api/ { - limit_req zone=api burst=10 nodelay; - - proxy_pass http://backend/; - 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; - - # CORS headers - add_header Access-Control-Allow-Origin * always; - add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; - add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always; - - if ($request_method = 'OPTIONS') { - add_header Access-Control-Allow-Origin *; - add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; - add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; - add_header Access-Control-Max-Age 1728000; - add_header Content-Type 'text/plain; charset=utf-8'; - add_header Content-Length 0; - return 204; - } - } - - # Health check endpoint - location /health { - access_log off; - proxy_pass http://backend/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; - add_header Content-Type application/json; - } - } -} \ No newline at end of file + } +}