Files
soroush.asadi d05b329c7a
CI/CD / CI · dotnet build (push) Successful in 1m22s
CI/CD / Deploy · hamkadr (push) Successful in 8s
Align deploy with central nginx: host-published 2569 + paste-in vhost (manual certs)
- Central nginx is containerized and proxies via host IP (171.22.25.73:port), not localhost → publish app on host :2569 (was 127.0.0.1)
- nginx vhost rewritten to match the monolithic config style (server blocks to paste into http{}, manual /etc/ssl/hamkadr certs, proxy_pass 171.22.25.73:2569, $connection_upgrade)
- DEPLOY.md: corrected architecture/ports, removed certbot+sites-available (use manual certs + single nginx.conf)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 05:09:39 +03:30

5.3 KiB

Deploying همکادر / hamkadr.ir

CI/CD via the soroush method: push to Gitea → Gitea Actions builds (through the Nexus mirror) and the self-hosted runner deploys with Docker Compose. nginx (already on the server) terminates TLS for hamkadr.ir and reverse-proxies to the app.

Architecture & open ports

Internet ─443/80─► central nginx (container)  ─► http://171.22.25.73:2569 ─► hamkadr_api (container :8080)
                   (nexus/mirror/gitea/meezi…)        host-published port            │ internal docker net
                                                                                      ▼
                                                                                hamkadr_db (postgres, no host port)

Matches the existing soroush pattern: the containerized central nginx reaches each app via the host IP + published port (171.22.25.73:<port>), not localhost. So hamkadr publishes 2569 on the host (like meezi 5080, draletaha 5010…).

Port Open? Purpose
22 (ideally IP-restricted) SSH
80 / 443 (already open) central nginx — serves hamkadr.ir too
2569 host-published app; only nginx proxies to it. Optionally firewall to the nginx host.
5432 internal docker net only Postgres — never published

No firewall change needed for 80/443 (nginx already serves git./mirror./meezi). 2569 is published on the host like the other apps.

Files in this repo

File Role
Dockerfile multi-stage build, images + NuGet via mirror.soroushasadi.com
nuget.docker.config NuGet → Nexus nuget-group
docker-compose.yml production stack: api (host :${HOST_PORT}) + db (internal) + named volume
docker-compose.dev.yml local-dev Postgres only (host 5433) for dotnet run
.gitea/workflows/ci-cd.yml build job + self-hosted deploy (backup → rollback tag → recreate → health-wait)
deploy/nginx-hamkadr.ir.conf nginx vhost for hamkadr.ir

One-time setup

1. DNS

A records → 171.22.25.73:

hamkadr.ir       A   171.22.25.73
www.hamkadr.ir   A   171.22.25.73

2. Gitea runner

Confirm the act_runner on this server has the self-hosted:host label (the deploy job needs it) and its user is in the docker group. (Already true if other soroush projects deploy here.)

3. ENV_FILE secret

Set at https://git.soroushasadi.com/soroushdes/hamkadr/settings/secrets → key ENV_FILE:

docker-compose.yml substitutes these into the api/db services (it derives ConnectionStrings__Default and Auth__AdminPhone for you), so the secret is just:

# host port nginx proxies to (must match deploy/nginx-hamkadr.ir.conf)
HOST_PORT=2569

# Postgres — generate a strong password: openssl rand -hex 24
POSTGRES_DB=hamkadr
POSTGRES_USER=hamkadr
POSTGRES_PASSWORD=__CHANGE_ME__

# Platform admin phone (gets the Admin role on login)
ADMIN_PHONE=09XXXXXXXXX

That's the whole secret. Everything else — the AI audit layer and the channel sources (Telegram channels, Bale bot token, Divar queries, auto-ingest on/off + interval) — is configured at runtime in the admin panel (/Admin/Settings), stored in the DB. No redeploy to change them. Defaults: AI off, mode = Manual, all sources off ⇒ nothing publishes without admin review. ASPNETCORE_ENVIRONMENT=Production is set by the compose file ⇒ only reference data (roles/cities/districts) is seeded — no demo facilities/shifts.

4. TLS cert + nginx vhost

Your central nginx is a single monolithic nginx.conf with manually-placed certs (no certbot). Match that:

  1. Put the hamkadr.ir cert where nginx expects (same convention as your other domains):
    /etc/ssl/hamkadr/fullchain.pem
    /etc/ssl/hamkadr/privateKey.pem
    
  2. Paste the two server { } blocks from deploy/nginx-hamkadr.ir.conf into the http { } block of your central nginx.conf (next to meezi/draletaha). They proxy to http://171.22.25.73:2569 and reuse the global $connection_upgrade map.
  3. Reload:
    nginx -t && nginx -s reload          # or: docker exec <nginx-container> nginx -s reload
    

5. First deploy

git push gitea main        # add the gitea remote first if needed

Watch https://git.soroushasadi.com/soroushdes/hamkadr/actions. The app auto-applies EF migrations on startup and seeds reference data; nginx already proxies hamkadr.ir to it.

Operations

  • Backups: every deploy runs pg_dump/opt/hamkadr-backups/hamkadr-<timestamp>.sql before touching containers.
  • Rollback: the previous image is tagged mirror.soroushasadi.com/hamkadr/api:rollback each deploy:
    docker stop hamkadr_api && docker rm hamkadr_api
    API_TAG=rollback docker compose up -d --no-deps api
    
  • Rotate a secret: edit ENV_FILE in Gitea, push any commit to redeploy.
  • Logs: docker logs -f hamkadr_api
  • Restore a backup: cat /opt/hamkadr-backups/<file>.sql | docker exec -i hamkadr_db psql -U hamkadr -d hamkadr

Safety (never do these)

  • docker compose down -v — deletes the database volume.
  • bare docker compose down / restart — would stop other projects on the shared host. The workflow always uses --no-deps <service> and explicit stop/rm.