# Dyolink — Staging deploy (Gitea + Windows) Automatic staging on a **self-hosted Gitea** machine: merge (or push) to **`master`** → build Docker images → push to Gitea Container Registry → deploy on the same host. **Example:** Gitea at `http://178.131.50.201:3000`, staging app at `http://178.131.50.201:8088`. Production (`nudentic.ir` on Linux, tag-based releases) is documented in [`DEPLOY.md`](DEPLOY.md). --- ## Architecture ``` Push / merge to master ↓ Gitea Actions (self-hosted act_runner on Windows) ↓ Build backend + frontend → push to Gitea registry ↓ docker compose pull + up -d (docker-compose.registry.yml) ↓ http://:8088 → nginx → frontend / backend → postgres ``` | Service | Image source | |----------|---------------------------------------------------| | postgres | `postgres:15-alpine` (pulled from Docker Hub) | | backend | `//dyolink-backend:` | | frontend | `//dyolink-frontend:` | | nginx | `nginx:alpine` | Frontend public URLs are **baked in at build time** via `PUBLIC_BASE_URL`. --- ## Triggers | Event | Staging deploy? | |-------|-----------------| | Push to `master` | Yes | | PR merged into `master` | Yes (merge = push to `master`) | | Push to other branches only | No | | Git tag (e.g. `v1.0.0`) | No — reserved for production later | | Manual | Gitea → Actions → **Registry — build, push, deploy** → Run workflow | Workflow file: [`.gitea/workflows/registry-build-deploy.yml`](../.gitea/workflows/registry-build-deploy.yml) --- ## One-time setup (Windows host) ### 1. Docker Desktop - Install and keep **Docker Desktop running** during builds/deploys. - **Settings → Docker Engine** — allow HTTP registry: ```json { "insecure-registries": [ "host.docker.internal:3000", "127.0.0.1:3000", "178.131.50.201:3000", "192.168.1.100:3000" ] } ``` Apply & restart Docker Desktop. **Why `host.docker.internal`?** Docker Desktop runs in a Linux VM. `docker login` runs from that VM — `127.0.0.1:3000` is the VM, not Gitea on Windows. ### 2b. Gitea `app.ini` — match registry URL Edit `C:\gitea\custom\conf\app.ini`: ```ini [server] ROOT_URL = http://host.docker.internal:3000/ ``` Restart Gitea. Gitea uses `ROOT_URL` for Docker registry auth redirects. ### 2c. Gitea Actions runner Download [act_runner](https://gitea.com/gitea/act_runner/releases) → e.g. `C:\gitea-runner\act_runner.exe`. **Important — runner level:** the registration token decides which repos can use the runner. | Token from | Runner level | Works for `admin/dyolink`? | |------------|--------------|----------------------------| | **Site Administration → Actions → Runners** | Instance (global) | Yes (recommended) | | **Repo → Settings → Actions → Runners** | Repository | Yes | | **User profile → Settings → Actions → Runners** | Individual | Often **no** — jobs stay queued with “no matching online runner” | If your runner shows **Type: Individual** in Gitea but jobs never start, delete it and re-register with an **instance** or **repository** token (table above). Register (use **localhost** when Gitea runs on the same PC — public IP often fails locally): ```powershell cd C:\gitea-runner # Stop daemon first (Ctrl+C) if running .\act_runner.exe register ` --instance "http://127.0.0.1:3000" ` --token "" ` --name "windows-staging" ` --labels "windows:host" ``` Start (leave running, or install as a Windows service later): ```powershell .\act_runner.exe daemon ``` **Verify:** open **your repo** → **Settings → Actions → Runners** — `windows-staging` must appear here as **Idle/Online** (not only under user settings). **Requires:** `git` on PATH (for workflow checkout step). ### 3. Secret env files (not in git) ```powershell New-Item -ItemType Directory -Force -Path "C:\dyolink\secrets" ``` Copy examples and edit: - `infrastructure/database.staging.env.example` → `C:\dyolink\secrets\database.staging.env` - `infrastructure/backend.staging.env.example` → `C:\dyolink\secrets\backend.staging.env` Rules: - `DATABASE_URL` password must match `POSTGRES_PASSWORD`. - `FRONTEND_URL` must match `PUBLIC_BASE_URL` (e.g. `http://178.131.50.201:8088`). - Replace JWT secrets with long random values (not `CHANGE_ME`). ### 4. Gitea repository Variables **Settings → Actions → Variables** | Name | Example | Notes | |------|---------|--------| | `REGISTRY_HOST` | `host.docker.internal:3000` | **Windows + Docker Desktop:** Docker runs in a Linux VM — `127.0.0.1` is the VM, not Gitea. Use `host.docker.internal:3000`. Also set Gitea `ROOT_URL` to match (see below). | | `REGISTRY_OWNER` | `admin` | Gitea user/org owning packages | | `PUBLIC_BASE_URL` | `http://178.131.50.201:8088` | How **users** open staging in a browser (public IP OK) | | `DEPLOY_SECRETS_DIR` | `C:/dyolink/secrets` | Forward slashes OK on Windows | | `CLONE_HOST` | `127.0.0.1:3000` | Git clone (runs on Windows host, not inside Docker VM) | | `STAGING_HTTP_PORT` | `8088` | Public port (router + portproxy). Optional. | | `STAGING_LOCAL_PORT` | `18088` | Docker bind on `127.0.0.1`. Must not be `8088` if portproxy already uses 8088. | **Naming note:** Gitea rejects variable names starting with `GITEA_` or `GITHUB_`. Use `CLONE_HOST`, not `GITEA_CLONE_URL`. ### 5. Gitea repository Secrets **Settings → Actions → Secrets** | Name | Value | |------|--------| | `REGISTRY_USERNAME` | Gitea username | | `REGISTRY_PASSWORD` | Gitea access token with **package read/write** | Create token: profile → **Settings → Applications → Generate New Token**. ### 6. Firewall (once) ```powershell New-NetFirewallRule -DisplayName "Dyolink Staging 8088" -Direction Inbound -Protocol TCP -LocalPort 8088 -Action Allow ``` ### 7. External access on Windows + Docker Desktop (portproxy) Gitea on **:3000** runs natively on Windows. Staging nginx binds **127.0.0.1:18088** (not 8088). Windows **portproxy** then maps public **8088 → 18088**. Do **not** bind Docker on 8088 — portproxy already owns that port (`bind: access permissions` if you try). Run **once** in **PowerShell as Administrator** (replace the old 8088→8088 rule if you had one): ```powershell netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=8088 netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=8088 connectaddress=127.0.0.1 connectport=18088 netsh interface portproxy show all Start-Service iphlpsvc Set-Service iphlpsvc -StartupType Automatic ``` Verify on the server: ```powershell curl http://127.0.0.1:18088/health curl http://127.0.0.1:8088/health ``` From your Mac: ```bash curl http://178.131.50.201:8088/health ``` If the public IP still fails but LAN works, add **router port forward 8088** → Windows PC (same as Gitea **3000**). To remove portproxy later: ```powershell netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=8088 ``` --- ## Test the pipeline ### Before first run - [ ] Docker Desktop running - [ ] `act_runner.exe daemon` running - [ ] All Variables + Secrets set (including `CLONE_HOST`) - [ ] Secret env files exist under `DEPLOY_SECRETS_DIR` - [ ] Workflow enabled on `master` (see repo) ### Option A — Manual workflow (safest first test) 1. Gitea → repo → **Actions** 2. **Registry — build, push, deploy** → **Run workflow** → branch `master` 3. Watch jobs: **build-and-push** → **deploy** (first run ~15–30 min) ### Option B — Push to master ```bash git push origin master ``` Or merge a PR into `master` — same result. ### Verify success **On Windows (PowerShell):** ```powershell docker ps ``` Expect: `dyolink_nginx_staging`, `dyolink_backend_staging`, `dyolink_frontend_staging`, `dyolink_postgres_staging`. **From browser or another machine:** ```text http://178.131.50.201:8088 ``` **Health check:** ```powershell curl http://178.131.50.201:8088/api/health ``` Expected: `{"status":"ok",...}` **Gitea packages:** profile/org → **Packages** — should list `dyolink-backend` and `dyolink-frontend` after first build. --- ## Manual deploy (without CI) Useful when debugging registry/compose without re-running the full workflow. On the Windows host, from repo `infrastructure/`: 1. Create `deploy.registry.env` from [`deploy.registry.env.example`](deploy.registry.env.example) 2. Set `REGISTRY_PREFIX`, `IMAGE_TAG`, `STAGING_HTTP_PORT`, `DEPLOY_SECRETS_DIR` 3. `docker login 178.131.50.201:3000 -u ` 4. `docker compose -f docker-compose.registry.yml --env-file deploy.registry.env pull backend frontend` 5. `docker compose -f docker-compose.registry.yml --env-file deploy.registry.env up -d` --- ## Troubleshooting | Symptom | Fix | |---------|-----| | `no matching online runner with label` | Runner **offline** → start `act_runner.exe daemon`. Or wrong **runner level** → re-register with token from **Site Administration → Actions → Runners** or **repo → Settings → Actions → Runners** (not user profile). Confirm runner appears on **repo** Runners page as Online. | | Runner can't register on public IP | Use `http://127.0.0.1:3000` for `--instance` | | Variable name rejected in Gitea | No `GITEA_*` / `GITHUB_*` prefixes; use `CLONE_HOST` | | `docker login` connection refused on `127.0.0.1:3000` | **Docker Desktop on Windows:** set `REGISTRY_HOST=host.docker.internal:3000`, add it to insecure-registries, set Gitea `ROOT_URL=http://host.docker.internal:3000/`. Keep `CLONE_HOST=127.0.0.1:3000` for git. | | `docker login` / push denied, redirect to public IP | Set Gitea `ROOT_URL` to a host Docker can reach (`host.docker.internal:3000` on Windows Docker Desktop). | | `server gave HTTP response to HTTPS client` | Add registry host to Docker **insecure-registries**, restart Docker | | `Missing database.staging.env` | Check `DEPLOY_SECRETS_DIR` path and file names | | `docker login` denied | Token needs package permissions; check username/secret | | Git clone fails in workflow | Set `CLONE_HOST=127.0.0.1:3000` | | Port 8088 bind forbidden / access permissions | Portproxy already owns 8088. Bind Docker to `18088` and point portproxy **8088 → 127.0.0.1:18088**. | | Port 8088 unreachable from Mac / empty reply | Docker Desktop: run **portproxy** (§7). `127.0.0.1:18088/health` must work on Windows first. | | Backend restart loop | JWT secrets still placeholder; fix `backend.staging.env` | | Backend DB auth error | `DATABASE_URL` password ≠ `POSTGRES_PASSWORD` | | `dumb-init docker-entrypoint.sh: No such file or directory` | Windows CRLF in shell scripts — fixed in Dockerfiles (rebuild images). | | `frontend is unhealthy` / deploy waits on frontend | Next.js `/` redirects to `/en` (3xx). Rebuild after healthcheck fix (accepts 2xx/3xx). | **Logs:** ```powershell docker logs dyolink_backend_staging --tail 50 docker logs dyolink_nginx_staging --tail 50 docker logs dyolink_frontend_staging --tail 50 ``` --- ## File reference | Path | Role | |------|------| | `.gitea/workflows/registry-build-deploy.yml` | CI: build, push, deploy | | `infrastructure/docker-compose.registry.yml` | Staging stack (pull-only images) | | `infrastructure/deploy.registry.env.example` | Manual deploy env template | | `infrastructure/database.staging.env.example` | Postgres secrets template | | `infrastructure/backend.staging.env.example` | API secrets template | | `infrastructure/nginx/http-only.conf` | HTTP reverse proxy for staging | --- ## Production (later) | Environment | Trigger | Host | |-------------|---------|------| | Staging | Push/merge to `master` | Windows + Gitea | | Production | Git tag `v*.*.*` | Linux + `nudentic.ir` | Production flow will use Docker Hub (or registry) + [`DEPLOY.md`](DEPLOY.md) — not yet wired to the same workflow.