Skip to content

Deployment

The platform is deployed with Docker Compose behind an edge nginx. The stack: PostgreSQL 16 (pgvector) + Redis 7 + the Go API + the Next.js BFF.

Host specifics are not on this page

Server addresses, paths and credentials live in the in-repository deploy/HOST.md runbook — this site is public, so it carries none of them.

Topology

Internet ──► edge nginx (80/443, Let's Encrypt)
   │           conf.d/geregeapp.mn.conf   ← owned by this repository
gerege-app-web (Next.js BFF)
   │  BACKEND_URL
internal compose network (no public ports): api ──► db + redis

The edge nginx is a separate compose project. The app's web container joins it through a shared docker network.

The vhost ownership model

Many ecosystem domains are served by one shared edge nginx whose conf.d directory is mounted from another repository's working copy — and that repository's deploy runs git reset --hard.

git reset --hard only restores tracked files; it leaves untracked files alone. So this repository installs its own vhost there as an untracked file and fully owns its configuration.

bash deploy/edge/install.sh    # update the vhost only (no rebuild)

What the script does:

  1. Finds the host path automatically from the edge container's /etc/nginx/conf.d mount.
  2. Backs up the previous version.
  3. Installs the new file (preserving directory ownership, 0644).
  4. Runs nginx -t — on failure it restores the previous version and does not reload.
  5. Runs nginx -s reload.
Env Default
EDGE_CONTAINER gerege-nginx
EDGE_CONF_D Auto-detected from the container mount
SKIP_EDGE Set to 1 for deploy.sh to skip it

Env files (gitignored)

File What Ownership
.env Compose interpolation (Postgres/Redis secrets, ports, origin) root, 0600 on the host
backend.env API configuration (JWT, eID, SSO, OIDC…) uid 65532, 0600

Ownership of backend.env

The api and migrate containers run as distroless nonroot (uid 65532). backend.env is mounted at /app/.env, so a root-owned 0600 file is unreadable — migrate fails immediately with failed to load config file.

sudo chown 65532:65532 <repo>/backend.env
sudo chmod 600 <repo>/backend.env

Never share secrets between deployments

Every deployment has its own JWT_SECRET, SSO_STATE_KEY, INTEGRATION_ENC_KEY and RP credentials. Generate them on the host with openssl rand — never through git, CI or chat.

Deploying

cd <repo path>
git fetch --prune origin && git reset --hard origin/main
bash deploy/deploy.sh          # build → up → wait for healthy → install edge

Properties of deploy.sh:

  • No-op guard — if nothing changed it does nothing at all (.deployed-sha).
  • Migration as its own step — this removed the 502 that used to accompany every deploy.
  • It does not rebuild db.

CI/CD

Pushing to main runs .github/workflows/ci.yml:

Job What
Backend gofmt -l . empty · go vet · go test -race · build
Frontend npm run lint · npm run test · npm run build
Secrets gitleaks
Deploy After all three pass, SSH in, git reset --hard <sha> and run deploy/deploy.sh

Required repo secrets: DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY, DEPLOY_PATH. The CI SSH key is separate from any operator key.

TLS

Let's Encrypt in webroot mode. Certificates live in an edge volume; a cron job attempts renewal twice a day and reloads nginx on success.

The vhost has its own listen 80 block (ACME challenge plus HTTPS redirect), so it does not depend on the shared default server and renewal works independently.

Diagnostics

docker compose ps
docker compose logs api --tail 50
docker compose logs migrate --tail 20     # "migration [up] success"

docker exec gerege-nginx nginx -t          # config validity
curl -s -o /dev/null -w '%{http_code}\n' https://geregeapp.mn/
Symptom Cause Fix
444 / dropped connection The vhost file was deleted (git clean -fd) bash deploy/edge/install.sh
502 Bad Gateway The web container is down or not on the edge network docker compose up -d; check the override file
conflicting server name A duplicate vhost in the shared config Remove it there — this repo owns the name
nginx will not start Missing certificate Obtain one with certbot
migrate failed to load config Ownership of backend.env chown 65532:65532

The host compose override

docker-compose.override.yml — which attaches the web container to the edge network and gives it a container_namelives only on the host (untracked).

  • Tracked in the repository, it would force production settings onto every developer's machine.
  • Not gitignored, git clean -fd would delete it and the container would leave the edge network, giving the vhost a 502.

Restoring it:

cp deploy/host-override.example.yml docker-compose.override.yml
docker compose up -d

This documentation site

The documentation is a separate static site served under docs.gerege.mn/app/:

Internet ──► gerege-nginx (docs.gerege.mn vhost)
               ├─ /       ─► docs-gerege-web     (ecosystem documentation)
               └─ /app/   ─► gerege-app-docs-web (this documentation)
bash docs-site/deploy/deploy.sh      # build → upload → refresh the container

Full runbook: docs-site/deploy/README.md in the repository.