Configuration and Operations Reference

Environment Variables

VariableRequiredDefaultPurpose
RACK_ENVYesdevelopmentproduction enables New Relic, disables source maps, precompiles assets
DATABASE_URLYesPostgreSQL connection string, e.g. postgres://user:pass@db:5432/18xx
APP_DATABASE_URLNofalls back to DATABASE_URLAlternative DB URL for the app (useful if queue uses a different connection)
NEW_RELIC_LICENSE_KEYNoEnables New Relic APM in production
ELASTIC_KEYNoElasticsearch integration key
SLACK_WEBHOOK_URLNoSlack webhook for deployment notifications
RUBYOPTNoSet to --yjit in production to enable YJIT JIT compiler
PORTNo9292Port the Unicorn server binds to
OPAL_PREFORK_DISABLENoSet to "true" to skip Opal prefork initialisation in production

Docker Compose Profiles

ProfileCommandPurpose
DevelopmentmakeRuns docker compose up with docker-compose.dev.yml override; uses rerun for auto-restart
Development (rebuild)make dev_up_bSame as above but forces a container rebuild
Productionmake prod_upUses docker-compose.prod.yml; Unicorn server, YJIT, asset precompilation
Production (rebuild)make prod_up_bForces rebuild before starting
Production (deploy)make prod_deployPulls latest master, rebuilds, restarts

Containers:

NameRoleExposed port
rackRoda web server (Unicorn)9292
rack_backupHot standby (production only)
queueRufus-Scheduler background jobs
dbPostgreSQL5433 (host)
redisRedis6380 (host)
nginxReverse proxy (production only)80, 443

Database

The database schema is managed by Sequel migrations in migrate/. To run all pending migrations:

docker compose exec rack bundle exec rake migrate

Database data is mounted from ./db/data on the host (UID 1000). To restore from backup:

  1. Stop the stack.
  2. Remove db/data/: rm -rf db/data.
  3. Restart the stack (creates a fresh cluster).
  4. Restore: docker compose exec -T db pg_restore ...

Background Jobs (queue.rb)

The queue container runs bundle exec ruby queue.rb. It registers one cron job:

Daily at 09:00 UTC [queue.rb:35]:

  1. Recalculates user statistics.
  2. Archives games: finished games older than 365 days and active games with no activity for 90 days. Archival removes the action list; only metadata is retained [queue.rb:38-48].
  3. Deletes unused pin bundles (retains the two newest) [queue.rb:50-62].
  4. Destroys unlisted new-status games older than 180 days; listed new games older than 14 days.

MessageBus subscribers in queue.rb:

ChannelTriggerAction
/turnAfter each accepted actionSends email or webhook notification to active players
/test_notificationManual testSends a test webhook to one user
/delete_userAccount deletionRemoves all games and the user record

Pinned Game Bundles

Old JavaScript bundles are stored at /18xx/public/pinned/<pin>.js.gz (inside the container; mounted from ./public on the host). The queue.rb cleanup job removes pin files that are no longer referenced by any active or finished game [queue.rb:50-62].

Logging

All containers write to stdout with max-size: 10m (development) or 50m (production) log rotation. Application logs use Ruby's Logger writing to $stdout. New Relic APM is active when NEW_RELIC_LICENSE_KEY is set and RACK_ENV=production.

What's next


Version: 2026-05-08 — derived from queue.rb, docker-compose.yml, docker-compose.prod.yml, Makefile, db.rb, DEVELOPMENT.md.