Skip to content

Deployment

This page is the condensed runbook. The authoritative, step-by-step ops document is deploy/README.md — fresh-server bootstrap, backups, migrations, and recovery live there.

Where production runs

One VPS at 200.141.2.84, Docker Compose, Caddy as the only container with published ports (80/443, automatic Let's Encrypt).

Domain Service Container
aimstoreorg.com, www.aimstoreorg.com website SPA + payment API aim_website (:3001)
api.aimstoreorg.com Go backend API aim_api (:8080)
admin.aimstoreorg.com admin portal aim_admin_portal (:3002)
docs.aimstoreorg.com this documentation aim_docs (:80)
monitor.aimstoreorg.com Grafana mon_grafana

Data stores (aim_postgres, aim_mongo, aim_redis) sit on the internal aim_data network and are never published. /metrics is 404'd at Caddy.

Server layout: /opt/aim-store/deploy (compose stack + Caddyfile + env), /opt/aim-store/monitoring, /opt/aim-store/backups, /opt/aim-store/static (bind-mounted into aim_api at /root/static for uploaded/team/player/KYC images).

How a deploy happens

Push to main; path filters trigger the matching workflow in .github/workflows/ (deploy-backend.yml, deploy-website.yml, deploy-admin-portal.yml, deploy-docs.yml, deploy-monitoring.yml; every workflow also has workflow_dispatch):

  1. build — Buildx builds the image and pushes latest + sha-<short> to ghcr.io/aimorgcode/{aim-api,aim-website,aim-admin,aim-docs}.
  2. deploy — copies the deploy/ (or monitoring/) configs to /opt/aim-store (git is the source of truth; server copies are overwritten), then over SSH — serialized with flock /tmp/aim-store-deploy.lock, set -e fail-fast — logs into GHCR and pulls the image.
  3. Backend only: pg_dump and mongodump into /opt/aim-store/backups (last 14 of each kept), then deploy/apply-migrations.sh runs pending SQL before the restart.
  4. docker compose up -d --no-build <service> caddy and a Caddy reload.
  5. verifycurl --resolve <domain>:443:200.141.2.84 https://<domain>/… health checks, retried. A red run means the deploy did not verify; the previous containers keep serving.

Mobile app releases (EAS Workflows)

The Expo app does not deploy to the VPS, and it is the one component not released by GitHub Actions. It uses EAS Workflows, which run on Expo's infrastructure and live in aim-store-app/.eas/workflows/ (beside eas.json, which is the EAS project root):

Workflow Trigger What it does
build-and-submit-android.yml push to main under aim-store-app/** (+ manual) Builds the Android app bundle, then submits it to Google Play
submit-android.yml manual only Submits one named build to a chosen track (internal / alpha / production)

A merge to main that touches the app is a full release to every user. The production submit profile in eas.json is releaseStatus: completed with no rollout fraction, so each release goes live to 100% of users at once — no staged rollout, and no human gate. To put a brake back on: set releaseStatus: inProgress with a rollout fraction (Play keeps its halt button), and/or add a require-approval job between the build and submit jobs.

.github/workflows/ci.yml still runs the app's typecheck and tests on the same paths, so the quality gate is unchanged; only building and releasing moved to EAS.

Two prerequisites live outside this repository, and the workflows are inert or failing until both exist:

  1. The GitHub repo must be linked to the EAS project (expo.dev → project → GitHub). Push triggers do not fire otherwise — silently.
  2. A Google Service Account Key must be registered in EAS credentials for com.aimstore.app. Without it every run fails at the submit job.

versionCode is allocated by EAS (appVersionSource: remote + autoIncrement), and signing uses the upload keystore in EAS credentials — neither is bumped or stored in this repo. Add [eas skip] to a commit message to skip a run.

Secrets

Names only — values are never committed (see ADR 0009):

  • GitHub repo secrets: VPS_HOST, VPS_USER, VPS_SSH_KEY, GHCR_PULL_USER, GHCR_PULL_TOKEN.
  • Server env files (chmod 600, generated by deploy/gen-env.sh): /opt/aim-store/deploy/.env, /opt/aim-store/deploy/portal.env.production, /opt/aim-store/monitoring/.env.

Rollback

Images are tagged sha-<short> on every build. Pin the affected service to a known-good tag in /opt/aim-store/deploy/.env and re-up:

# on the VPS
cd /opt/aim-store/deploy
echo 'API_IMAGE_TAG=sha-a1b2c3d' >> .env    # or WEBSITE_IMAGE_TAG /
                                            # ADMIN_PORTAL_IMAGE_TAG / DOCS_IMAGE_TAG
docker compose up -d --no-build api

Database restores come from /opt/aim-store/backups (pg_dump + mongodump, 14 most recent of each) — procedure in deploy/README.md.