Four services in one Railway project: web, engine, PostgreSQL and site (the documentation published at https://trading.vukem.dev). Everything is paper trading.
Services are connected to the GitHub repository and branch
main: a push tomaindeploys. Use pull requests and wait for green CI before merging.
Topology#
| Service | Source | Public | Notes |
|---|---|---|---|
| web | Root Dockerfile, start /app/app | HTTPS domain | Runs migrations in the pre-deploy step; healthcheck /ready |
| engine | Root Dockerfile, start /app/engine | No domain | Paper execution loop; healthcheck /ready on ENGINE_HEALTH_PORT |
| PostgreSQL | Railway database | Private network | Daily backup; see backups |
| site | site/Dockerfile, build context repository root | trading.vukem.dev | Next.js documentation, static pages |
Keep one replica of the engine and never scale it by changing the replica count: leader election and fencing protect against duplicate execution, not against a deliberate fan-out.
Create the project#
- Install the Railway CLI and log in:
railway login. - Create or link the project:
railway link. - Add PostgreSQL from the Railway dashboard (Database, PostgreSQL).
- Create the services from the GitHub repository
AlexGalhardo/vukem-trading, branchmain.
Web service#
-
Builder: Dockerfile, path
Dockerfile. -
Pre-deploy command:
/app/app migrate. Start command:/app/app. Healthcheck path/ready, timeout 120 seconds. Restart on failure, up to 5 times. -
Variables (template:
.env.railway.web.example; copy it to a.localfile that is ignored by Git):Text APP_ENV=production APP_DEBUG=false APP_KEY=<generated once, shared with the engine> APP_URL=https://<web domain> DATABASE_URL=${{Postgres.DATABASE_URL}} SESSION_DRIVER=database CACHE_STORE=database MAIL_MAILER=log OWNER_GOOGLE_EMAIL=<your Google account> SOCIAL_GOOGLE_CLIENT_ID=<Google OAuth client> SOCIAL_GOOGLE_CLIENT_SECRET=<Google OAuth secret> -
Google callback:
https://<web domain>/auth/google/callback. -
Optional:
ALPACA_API_KEYandALPACA_API_SECRET(paper credentials only),TELEGRAM_BOT_TOKENandTELEGRAM_CHAT_ID.
Engine service#
- Same Dockerfile; start command
/app/engine; no public domain. - Variables (template:
.env.railway.engine.example): the sameAPP_KEY,DATABASE_URL,SESSION_DRIVER,CACHE_STORE,APP_ENV, plus Alpaca and Telegram values if used. Railway providesPORT; do not copy the localENGINE_HEALTH_PORT. - Broker credentials live only here and in the web service for the IEX import. Never put them in
VITE_*variables.
Site service and DNS#
- Create a service from the same repository. Set
RAILWAY_DOCKERFILE_PATH=site/Dockerfile. The build context is the repository root because the site readsdocs/andCHANGELOG.md. - Add the custom domain
trading.vukem.devin the service's Networking settings. Railway shows the DNS target. - Create the record at your DNS provider exactly as Railway shows it (a
CNAMEfortradingpointing at the Railway target, plus the verificationTXTrecord if requested). Check it before applying: a wrong record can take down another service on the same zone. - Wait for the certificate and open https://trading.vukem.dev. The root path redirects to the introduction page.
Deploy, rollback and logs#
- Deploy: merge a pull request into
mainafter CI is green. Manual redeploy:railway redeploy. - Status and logs:
railway deployment list --json,railway logs. - Rollback: redeploy the previous successful deployment from the dashboard, or revert the commit.
- Smoke test after each deploy:
/returns 302 to login,/login200,/ready200,/api/snapshotwithout a session 401.
Backups#
Enable the daily PostgreSQL backup schedule in the dashboard and confirm the provider's retention (six days at the time of writing; complement it if you need seven). Run a restore into a scratch database before you rely on it. scripts/backup.sh creates an off-platform dump.
Costs and region#
Railway bills by usage. Confirm region, plan, CPU and memory, and a monthly budget alert before enabling the engine continuously. The region is inherited from the project; do not assume TimescaleDB is available.
Checklist#
- PostgreSQL attached and the
Postgresreference name matchesDATABASE_URL. - Same
APP_KEYin web and engine; generated withvukem key:generate, never committed. - Google OAuth callback matches the web domain.
- Engine has no public domain and one replica.
- Healthchecks pass; smoke test done.
- Backup schedule enabled and a restore tested.
-
trading.vukem.devresolves and serves the documentation.