Align CI/CD with soroush method (DrSousan single-app pattern)
Audited against working Meezi/DrSousan pipelines. Fixes:
- Single docker-compose.yml is the production stack (api + internal db); folded in docker-compose.prod.yml; dev Postgres → docker-compose.dev.yml
- Dockerfile HEALTHCHECK (bash /dev/tcp) so deploy's docker-inspect Health.Status wait works
- Naming to convention: service api, container hamkadr_api/hamkadr_db, image mirror.soroushasadi.com/hamkadr/api:${API_TAG}
- Workflow rewritten to DrSousan pattern: ci build + deploy (rollback-tag before build, pg_dump backup, stop/rm/up, docker-inspect health-wait with crash detection, scoped image prune)
- environment: block with ${VAR:-default} substitution (no hard-failing env_file); HOST_PORT; .env excluded from image context
- nginx vhost + DEPLOY.md updated to match
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,10 +7,10 @@ TLS for `hamkadr.ir` and reverse-proxies to the app.
|
||||
## Architecture & open ports
|
||||
|
||||
```
|
||||
Internet ──443/80──► nginx (host, existing) ──► 127.0.0.1:8090 ──► hamkadr-app (container :8080)
|
||||
Internet ──443/80──► nginx (host, existing) ──► 127.0.0.1:8090 ──► hamkadr_api (container :8080)
|
||||
│ internal docker net
|
||||
▼
|
||||
hamkadr-db (postgres, no host port)
|
||||
hamkadr_db (postgres, no host port)
|
||||
```
|
||||
|
||||
| Port | Open? | Purpose |
|
||||
@@ -30,7 +30,8 @@ serves git./mirror. — no firewall change needed.)
|
||||
|------|------|
|
||||
| `Dockerfile` | multi-stage build, images + NuGet via `mirror.soroushasadi.com` |
|
||||
| `nuget.docker.config` | NuGet → Nexus `nuget-group` |
|
||||
| `docker-compose.prod.yml` | `app` (127.0.0.1:${APP_PORT}) + `db` (internal) + named volume |
|
||||
| `docker-compose.yml` | production stack: `api` (127.0.0.1:${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 |
|
||||
|
||||
@@ -50,48 +51,37 @@ and its user is in the `docker` group. (Already true if other soroush projects d
|
||||
### 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:
|
||||
|
||||
```dotenv
|
||||
ASPNETCORE_ENVIRONMENT=Production
|
||||
ASPNETCORE_URLS=http://+:8080
|
||||
|
||||
# host port nginx proxies to (must match deploy/nginx-hamkadr.ir.conf)
|
||||
APP_PORT=8090
|
||||
HOST_PORT=8090
|
||||
|
||||
# Postgres (container) — generate a strong password: openssl rand -hex 24
|
||||
# Postgres — generate a strong password: openssl rand -hex 24
|
||||
POSTGRES_DB=hamkadr
|
||||
POSTGRES_USER=hamkadr
|
||||
POSTGRES_PASSWORD=__CHANGE_ME__
|
||||
|
||||
# EF Core connection string (host = compose service name "db")
|
||||
ConnectionStrings__Default=Host=db;Port=5432;Database=hamkadr;Username=hamkadr;Password=__CHANGE_ME__
|
||||
# Platform admin phone (gets the Admin role on login)
|
||||
ADMIN_PHONE=09XXXXXXXXX
|
||||
|
||||
# Platform admin (the phone that gets the Admin role on login)
|
||||
Auth__AdminPhone=09XXXXXXXXX
|
||||
|
||||
# Future: Kavenegar / SMS.ir keys for real OTP delivery
|
||||
|
||||
# --- Channel scraping (optional; off by default) ---
|
||||
# Enable the background worker and the sources you want, then their fetch runs on a timer.
|
||||
# Ingestion__Enabled=true
|
||||
# Ingestion__IntervalMinutes=30
|
||||
# Telegram (public channels via t.me/s — no token needed):
|
||||
# Ingestion__Telegram__Enabled=true
|
||||
# Ingestion__Telegram__Channels__0=shift_channel_username
|
||||
# Ingestion__Telegram__Channels__1=another_channel
|
||||
# Bale (bot must be a member of the channel; Telegram-style Bot API):
|
||||
# Ingestion__Bale__Enabled=true
|
||||
# Ingestion__Bale__BotToken=__BALE_BOT_TOKEN__
|
||||
# Divar (best-effort web-search):
|
||||
# Ingestion__Divar__Enabled=true
|
||||
# Ingestion__Divar__Queries__0=استخدام پزشک
|
||||
# Ingestion__Divar__Queries__1=پرستار
|
||||
# --- Channel scraping (optional; off by default) — toggles ---
|
||||
# INGESTION_ENABLED=true
|
||||
# INGESTION_INTERVAL_MINUTES=30
|
||||
# TELEGRAM_ENABLED=true
|
||||
# TELEGRAM_BOT_TOKEN=__TELEGRAM_BOT_TOKEN__
|
||||
# BALE_ENABLED=true
|
||||
# BALE_BOT_TOKEN=__BALE_BOT_TOKEN__
|
||||
# DIVAR_ENABLED=true
|
||||
```
|
||||
> Channel **lists** (`Telegram.Channels`, `Divar.Queries`) live in `appsettings.json` (or add
|
||||
> `Ingestion__Telegram__Channels__0=...` keys). The toggles above gate each source on/off.
|
||||
> The **AI audit layer** is configured at runtime in the admin panel (`/Admin/Settings`) — endpoint,
|
||||
> model, API key, prompt/framework, and auto-approve — not via env. Default: AI off, mode = Manual,
|
||||
> model, API key, prompt/framework, auto-approve — not via env. Default: AI off, mode = Manual,
|
||||
> so every ingested listing waits in the review queue until an admin publishes it.
|
||||
> `POSTGRES_PASSWORD` and the password in `ConnectionStrings__Default` must be identical.
|
||||
> `ASPNETCORE_ENVIRONMENT=Production` ⇒ only **reference data** (roles/cities/districts) is seeded —
|
||||
> no demo facilities/shifts. Real employers add listings via the employer panel.
|
||||
> `ASPNETCORE_ENVIRONMENT=Production` is set by the compose file ⇒ only **reference data**
|
||||
> (roles/cities/districts) is seeded — no demo facilities/shifts.
|
||||
|
||||
### 4. nginx vhost + TLS
|
||||
```bash
|
||||
@@ -111,15 +101,14 @@ 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 `hamkadr-app:rollback` each deploy:
|
||||
- **Rollback:** the previous image is tagged `mirror.soroushasadi.com/hamkadr/api:rollback` each deploy:
|
||||
```bash
|
||||
docker stop hamkadr-app && docker rm hamkadr-app
|
||||
docker run -d --name hamkadr-app --env-file .env --network hamkadr_default \
|
||||
-p 127.0.0.1:8090:8080 hamkadr-app:rollback
|
||||
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-app`
|
||||
- **Restore a backup:** `cat /opt/hamkadr-backups/<file>.sql | docker exec -i hamkadr-db psql -U hamkadr -d hamkadr`
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user