Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sdurutr436/stay-sidekick/llms.txt

Use this file to discover all available pages before exploring further.

Railway runs Stay Sidekick’s five services in separate containers connected via a private internal network. Only the nginx service is exposed publicly — all other services communicate over Railway’s private .railway.internal DNS. TLS is terminated at Railway’s edge automatically, so the nginx container itself only listens on port 80 and never manages certificates.

Architecture

Internet (HTTPS)

   [nginx]  ← only public service
      ↓ .railway.internal
   ├── [frontend]  Angular SPA    :80
   ├── [web]       11ty static    :80
   └── [backend]   Flask API      :5000

                 [PostgreSQL]  Railway Database Plugin
Each service is built from the same Git repository, using the same Dockerfiles as the local Docker Compose setup. The only behavioural difference in production is that nginx loads nginx.railway.conf instead of nginx.conf, activated by the RAILWAY=true environment variable.

Step-by-step deployment

1

Create a new Railway project

Log in to railway.app, click New Project, and select Deploy from GitHub repo. Authorize Railway to access your GitHub account and select the stay-sidekick repository.
2

Add all five services

Railway creates one service from the repository by default. Add the remaining four:
Service nameType
nginxGitHub repo
frontendGitHub repo
webGitHub repo
backendGitHub repo
postgresDatabase Plugin (PostgreSQL)
For the Database Plugin, click + NewDatabaseAdd PostgreSQL. Railway provisions a managed PostgreSQL 16 instance and makes its connection details available as variables.
3

Configure Root Directory for each service

Each service needs to know which subdirectory contains its Dockerfile. Set the Root Directory in each service’s Settings tab:
ServiceRoot DirectoryNotes
nginxnginx/
frontendfrontend/
web(leave empty)Must use the repo root — see warning below
backendbackend/
The web service (11ty static site) must have an empty Root Directory (i.e. the repository root). Its Dockerfile compiles SCSS from frontend/src/styles/, which lives outside the web/ directory. Setting Root Directory to web/ would place that path out of the build context and break the SCSS compilation step.
For the web service, Railway reads railway.toml from the repo root which points to web/Dockerfile:
[build]
builder = "DOCKERFILE"
dockerfilePath = "web/Dockerfile"

[deploy]
healthcheckPath = "/"
healthcheckTimeout = 90
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3
4

Activate the Railway nginx configuration

In the nginx service’s Variables tab, add:
RAILWAY=true
This build argument causes the nginx Dockerfile to copy nginx.railway.conf instead of nginx.conf. The Railway configuration uses .railway.internal hostnames to reach the other services over the private network and sets a low DNS TTL to handle service redeployments gracefully.
5

Add backend environment variables

In the backend service’s Variables tab, add all required variables. Refer to the Environment Variables reference for the complete list. At a minimum, the following must be set to non-placeholder values:
VariableNotes
FLASK_ENVSet to production
SECRET_KEY≥ 32 random characters
JWT_SECRET_KEY≥ 32 random characters
FERNET_KEYGenerate with Fernet (see env vars page)
ALLOWED_ORIGINSThe public HTTPS URL of your nginx service
FRONTEND_BASE_URLSame as ALLOWED_ORIGINS
GOOGLE_REDIRECT_URIhttps://your-domain/api/contactos/google/callback
6

Set DATABASE_URL as a variable reference

In the backend service’s Variables tab, add DATABASE_URL as a Railway variable reference — not a hardcoded string:
DATABASE_URL=${{ Postgres.DATABASE_URL }}
Railway resolves this reference at deploy time using the connection string from the PostgreSQL plugin. If Railway ever rotates the database credentials, the reference updates automatically without any manual intervention.
Always use ${{ Postgres.DATABASE_URL }} as a variable reference rather than copying the raw connection string. Hardcoded connection strings break silently when Railway rotates credentials during maintenance.
7

Enable Wait for CI on each service

In each service’s Settings tab, enable Wait for CI (or PR Checks). This prevents Railway from deploying a broken build if any GitHub Actions workflow (ci-python, ci-angular, ci-web) fails on the same commit.
8

Trigger the first deploy

Click Deploy in each service’s dashboard panel, or push a commit to main. Railway builds each Dockerfile in parallel. Monitor progress in the Deployments tab of each service.After all five services report a green status, verify the API health endpoint through the nginx public domain:
curl -s https://your-nginx-domain.railway.app/api/health
# {"status": "ok"}

HTTPS and TLS

Railway terminates TLS at its edge for any service that has a public domain assigned. From the perspective of the nginx container, all traffic arrives as plain HTTP on port 80 — no certbot, no ssl_certificate directives, no port 443 listener. Security headers (Strict-Transport-Security, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) are added by nginx on every response regardless of where TLS is terminated, so they are present end-to-end. To assign a public domain to the nginx service, go to its Settings tab and click Generate Domain (Railway subdomain) or Custom Domain (your own).

nginx.railway.conf

When the nginx service is built with RAILWAY=true, the Dockerfile copies nginx.railway.conf into the image. This configuration differs from the local nginx.conf in two important ways:
  • Upstream hostnames point to *.railway.internal private DNS names (e.g. frontend.railway.internal, backend.railway.internal) instead of the Docker Compose service names.
  • DNS resolver TTL is kept deliberately short so that nginx picks up new IP addresses quickly after a service is redeployed. Without this, nginx caches the old IP until the worker restarts, causing 502 Bad Gateway errors during rolling updates.
If you see 502 Bad Gateway on Railway, confirm that:
  1. All service names in nginx.railway.conf match the Railway service names exactly (they are case-sensitive).
  2. The nginx service has been redeployed after any rename of a dependent service.

Build docs developers (and LLMs) love