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):
- build — Buildx builds the image and pushes
latest+sha-<short>toghcr.io/aimorgcode/{aim-api,aim-website,aim-admin,aim-docs}. - deploy — copies the
deploy/(ormonitoring/) configs to/opt/aim-store(git is the source of truth; server copies are overwritten), then over SSH — serialized withflock /tmp/aim-store-deploy.lock,set -efail-fast — logs into GHCR and pulls the image. - Backend only:
pg_dumpandmongodumpinto/opt/aim-store/backups(last 14 of each kept), thendeploy/apply-migrations.shruns pending SQL before the restart. docker compose up -d --no-build <service> caddyand a Caddy reload.- verify —
curl --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:
- The GitHub repo must be linked to the EAS project (expo.dev → project → GitHub). Push triggers do not fire otherwise — silently.
- 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.