- Shell 100%
| deploy | ||
| .gitignore | ||
| README.md | ||
apps-infra
Shared deployment repository for backend services on one VPS.
Target layout on server:
/srv/apps/
analytics-api/
ruwords-api/
apps-infra/
apps-infra/deploy/docker-compose.yml builds services from sibling repos (../analytics-api, ../ruwords-api) and runs them behind one Caddy instance.
Use deploy/Caddyfile with the shared compose stack. deploy/Caddyfile.base is a legacy reference from the pre-split deployment and does not match the current service names.
Services
- Universal analytics API (prod + staging)
- RuWords API (prod + staging)
- Shared Postgres instance (separate databases)
- Shared Caddy (separate domains)
Gateway policy (Caddy)
- Analytics domains expose only:
GET /healthzPOST /v1/events
- RuWords domains expose only:
GET /healthzGET /content/v1/*(requiresAuthorization: Bearer <CONTENT_READ_TOKEN_...>at gateway)/v1/*leaderboard API routes (auth handled by ruwords-api service token)
- Other paths return
404(or401for missing/invalid content token).
Production prerequisites
Create deploy/.env from deploy/.env.example and keep it uncommitted. It contains domains, database credentials, token peppers, content read tokens, and leaderboard bearer tokens.
DNS for all configured domains must point at the target VPS before starting Caddy, otherwise Let's Encrypt certificate issuance will fail.
RuWords content paths must use the release layout expected by ruwords-api:
/srv/content/ruwords/prod/
current -> releases/<release_id>
releases/<release_id>/manifest.json
releases/<release_id>/packs/<pack_id>/<version>.json
/srv/content/ruwords/staging/
current -> releases/<release_id>
releases/<release_id>/manifest.json
releases/<release_id>/packs/<pack_id>/<version>.json
Analytics migrations create the apps, ingest_tokens, and events_raw tables, but do not create app rows or ingest tokens. Seed production/staging apps and token hashes after migrations. The service checks bearer tokens by storing sha256(TOKEN_PEPPER + ':' + token) in ingest_tokens.token_hash.
Backups must cover deploy/.env, the repository revisions, RuWords content releases, Caddy data/config volumes, and Postgres logical dumps. Do not rely on copying the live Postgres volume as the only database backup.
Analytics Ingest Tokens
After analytics migrations have run, seed one token per app/environment. Store the plaintext token in the app's secret storage or release configuration; the database stores only the derived hash.
export ANALYTICS_INGEST_TOKEN='replace_with_token_from_password_manager'
deploy/scripts/analytics-token.sh seed prod ruwords-ios "RuWords iOS"
deploy/scripts/analytics-token.sh seed staging ruwords-ios-staging "RuWords iOS Staging"
Revoke a token by providing the same plaintext token:
export ANALYTICS_INGEST_TOKEN='token_to_revoke'
deploy/scripts/analytics-token.sh revoke prod
Quick start
cp deploy/.env.example deploy/.env
# edit domains, secrets, and mount paths
docker compose -f deploy/docker-compose.yml up -d --build
# run DB migrations
docker compose -f deploy/docker-compose.yml --profile tools run --rm migrate_analytics_prod
docker compose -f deploy/docker-compose.yml --profile tools run --rm migrate_analytics_staging
docker compose -f deploy/docker-compose.yml --profile tools run --rm migrate_ruwords_prod
docker compose -f deploy/docker-compose.yml --profile tools run --rm migrate_ruwords_staging