Skip to content

Commit a63ba5a

Browse files
feat: use agronom.fastergo.uz domain for staging with nginx + SSL
- Update staging.conf: domain staging-api.fastergo.uz → agronom.fastergo.uz - Add /uploads/ static file location in nginx (served from uploads volume) - Restore nginx + certbot SSL block in CD pipeline with new domain - FileStorage__BaseUrl → https://agronom.fastergo.uz/uploads - Smoke test hits https://agronom.fastergo.uz instead of raw IP:port - Mount uploads volume into nginx container for direct static file serving Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent f207976 commit a63ba5a

File tree

2 files changed

+81
-19
lines changed

2 files changed

+81
-19
lines changed

.github/workflows/cd.yml

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ jobs:
101101
needs: build-and-push
102102
environment:
103103
name: staging
104-
url: http://${{ secrets.STAGING_HOST }}:8080
104+
url: https://agronom.fastergo.uz
105105
if: >-
106106
github.ref == 'refs/heads/master' ||
107107
startsWith(github.ref, 'refs/tags/v') ||
@@ -142,6 +142,17 @@ jobs:
142142
ssh-keyscan -H "${SSH_HOST}" >> ~/.ssh/known_hosts 2>/dev/null
143143
echo "SSH configured for ${SSH_HOST}"
144144
145+
- name: Copy nginx config to staging server
146+
env:
147+
SSH_HOST: ${{ secrets.STAGING_HOST }}
148+
SSH_USER: ${{ secrets.STAGING_USER }}
149+
run: |
150+
ssh -i ~/.ssh/deploy_key "${SSH_USER}@${SSH_HOST}" \
151+
mkdir -p /opt/aqlliagronom/staging/nginx
152+
scp -i ~/.ssh/deploy_key \
153+
nginx/conf.d/staging.conf \
154+
"${SSH_USER}@${SSH_HOST}:/opt/aqlliagronom/staging/nginx/app.conf"
155+
145156
- name: Deploy to staging via SSH
146157
env:
147158
SSH_HOST: ${{ secrets.STAGING_HOST }}
@@ -289,7 +300,7 @@ jobs:
289300
-e "Qdrant__Host=\${QDRANT_CONTAINER}" \
290301
-e "Qdrant__ApiKey=\${QDRANT_KEY}" \
291302
-e "FileStorage__BasePath=/app/uploads" \
292-
-e "FileStorage__BaseUrl=http://\${SSH_HOST}:8080/uploads" \
303+
-e "FileStorage__BaseUrl=https://agronom.fastergo.uz/uploads" \
293304
-v /opt/aqlliagronom/staging/logs:/app/logs \
294305
-v staging_uploads_data:/app/uploads \
295306
"\${IMAGE_FULL}"
@@ -311,28 +322,75 @@ jobs:
311322
sleep 5
312323
done
313324
314-
# ── Nginx / SSL — skipped for staging (no domain, accessed via IP:8080) ──
315-
echo "==> Staging accessible at http://\$(hostname -I | awk '{print \$1}'):8080"
325+
# ── Nginx + Certbot (SSL) ─────────────────────────────────────────────
326+
NGINX_DOMAIN="agronom.fastergo.uz"
327+
NGINX_CONF_DIR="/opt/aqlliagronom/staging/nginx"
328+
329+
docker volume create certbot_staging_certs 2>/dev/null || true
330+
docker volume create certbot_staging_www 2>/dev/null || true
331+
332+
# Generate temporary self-signed cert on first deploy so nginx can start
333+
if ! docker run --rm -v certbot_staging_certs:/etc/letsencrypt alpine \
334+
test -f "/etc/letsencrypt/live/\${NGINX_DOMAIN}/fullchain.pem" 2>/dev/null; then
335+
echo "==> Generating temporary self-signed cert for \${NGINX_DOMAIN}..."
336+
docker run --rm -v certbot_staging_certs:/etc/letsencrypt alpine sh -c \
337+
"apk add --no-cache openssl >/dev/null 2>&1 && \
338+
mkdir -p /etc/letsencrypt/live/\${NGINX_DOMAIN} && \
339+
openssl req -x509 -nodes -newkey rsa:2048 \
340+
-keyout /etc/letsencrypt/live/\${NGINX_DOMAIN}/privkey.pem \
341+
-out /etc/letsencrypt/live/\${NGINX_DOMAIN}/fullchain.pem \
342+
-days 1 -subj /CN=\${NGINX_DOMAIN} 2>/dev/null"
343+
fi
344+
345+
if docker ps --format '{{.Names}}' | grep -q '^aqlliagronom-nginx\$'; then
346+
echo "==> Reloading nginx config..."
347+
docker exec aqlliagronom-nginx nginx -s reload || true
348+
else
349+
docker stop aqlliagronom-nginx 2>/dev/null || true
350+
docker rm aqlliagronom-nginx 2>/dev/null || true
351+
echo "==> Starting nginx..."
352+
docker run -d \
353+
--name aqlliagronom-nginx \
354+
--restart unless-stopped \
355+
--network aqlliagronom-network \
356+
-p 80:80 -p 443:443 \
357+
-v "\${NGINX_CONF_DIR}:/etc/nginx/conf.d:ro" \
358+
-v certbot_staging_certs:/etc/letsencrypt:ro \
359+
-v certbot_staging_www:/var/www/certbot:ro \
360+
-v staging_uploads_data:/app/uploads:ro \
361+
nginx:1.28-alpine
362+
fi
363+
364+
if [ -n "\${CERTBOT_EMAIL}" ]; then
365+
echo "==> Running certbot for \${NGINX_DOMAIN}..."
366+
docker run --rm \
367+
-v certbot_staging_certs:/etc/letsencrypt \
368+
-v certbot_staging_www:/var/www/certbot \
369+
certbot/certbot certonly \
370+
--webroot -w /var/www/certbot \
371+
-d "\${NGINX_DOMAIN}" \
372+
--non-interactive --agree-tos \
373+
-m "\${CERTBOT_EMAIL}" \
374+
--keep-until-expiring 2>&1 | tail -5 || true
375+
docker exec aqlliagronom-nginx nginx -s reload 2>/dev/null || true
376+
fi
377+
echo "==> https://\${NGINX_DOMAIN} ready"
316378
317379
docker image prune -f || true
318380
ENDSSH
319381
320382
# ── 3. Smoke Test Staging ─────────────────────────────────────────────────────
321-
# Tests against http://<host>:8080 (direct Docker port) — no nginx/TLS needed.
322-
# Switch to https://staging-api.aqlliagronom.uz once a reverse proxy is set up.
323383
smoke-test-staging:
324384
name: Smoke Test — Staging
325385
runs-on: ubuntu-latest
326386
needs: deploy-staging
327387

328388
steps:
329389
- name: Health check with retries
330-
env:
331-
STAGING_HOST: ${{ secrets.STAGING_HOST }}
332390
run: |
333-
BASE="http://${STAGING_HOST}:8080"
391+
BASE="https://agronom.fastergo.uz"
334392
for i in $(seq 1 6); do
335-
STATUS=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 "${BASE}/health/live")
393+
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 10 "${BASE}/health/live")
336394
if [ "$STATUS" = "200" ]; then
337395
echo "Health check passed (HTTP $STATUS)"
338396
break
@@ -346,11 +404,9 @@ jobs:
346404
done
347405
348406
- name: API smoke test
349-
env:
350-
STAGING_HOST: ${{ secrets.STAGING_HOST }}
351407
run: |
352-
BASE="http://${STAGING_HOST}:8080"
353-
STATUS=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 \
408+
BASE="https://agronom.fastergo.uz"
409+
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 10 \
354410
-X POST "${BASE}/api/v1/auth/register" \
355411
-H "Content-Type: application/json" \
356412
-d '{"fullName":"","phone":"","password":""}')

nginx/conf.d/staging.conf

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Staging — staging-api.fastergo.uz
1+
# Staging — agronom.fastergo.uz
22
# Used by the CD pipeline on the staging server.
33
# Upstream container name matches the one started by cd.yml: aqlliagronom-staging
44

55
server {
66
listen 80;
7-
server_name staging-api.fastergo.uz;
7+
server_name agronom.fastergo.uz;
88

99
# Let's Encrypt ACME webroot challenge
1010
location /.well-known/acme-challenge/ {
@@ -18,10 +18,10 @@ server {
1818

1919
server {
2020
listen 443 ssl;
21-
server_name staging-api.fastergo.uz;
21+
server_name agronom.fastergo.uz;
2222

23-
ssl_certificate /etc/letsencrypt/live/staging-api.fastergo.uz/fullchain.pem;
24-
ssl_certificate_key /etc/letsencrypt/live/staging-api.fastergo.uz/privkey.pem;
23+
ssl_certificate /etc/letsencrypt/live/agronom.fastergo.uz/fullchain.pem;
24+
ssl_certificate_key /etc/letsencrypt/live/agronom.fastergo.uz/privkey.pem;
2525
ssl_protocols TLSv1.2 TLSv1.3;
2626
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
2727
ssl_prefer_server_ciphers off;
@@ -39,6 +39,12 @@ server {
3939
resolver 127.0.0.11 valid=30s;
4040
set $upstream aqlliagronom-staging;
4141

42+
location /uploads/ {
43+
alias /app/uploads/;
44+
expires 30d;
45+
add_header Cache-Control "public, immutable";
46+
}
47+
4248
location / {
4349
proxy_pass http://$upstream:8080;
4450
proxy_http_version 1.1;

0 commit comments

Comments
 (0)