# Infrastructure The site on Railway: one project, `bible`, with a `production` and a `dev` environment, and one service, `site`, that runs the repo's `Dockerfile` in Virginia (`us-east4-eqdc4a`). Railway holds the certificates; `server.py` does the routing there as it does locally. DNS stays on Cloudflare. | env | Railway environment | URL | |------|---------------------|-------------------------------| | prod | `production` | https://bible-2.aerlinn.net | | dev | `dev` | https://bible-dev.aerlinn.net | ``` railway/ project, environments, service, custom domains, and their CNAME + TXT records on Cloudflare railway-cli/ Railway's CLI as a pinned image (theirs is x86-only) ``` ## Tools Nothing to install but Docker. Terraform and Railway's CLI are services in `compose.yaml` (profile `tools`), reading tokens from `.env`. From the repo root: ``` docker compose run --rm terraform -chdir=infra/railway plan docker compose run --rm railway status ``` Commit `.terraform.lock.hcl`; it pins the provider builds. ## First-time setup 1. **Tokens.** A Railway workspace token (account settings -> Tokens, with the workspace selected; a project token cannot make projects), the Cloudflare token, and the state bucket's keys (below). `cp .env.example .env` and fill them in. 2. **Project and domains.** ``` docker compose run --rm terraform -chdir=infra/railway init docker compose run --rm terraform -chdir=infra/railway apply ``` 3. **Deploy**, below. Railway issues each certificate once it sees the CNAME; the first HTTPS requests can fail for a minute or two until then. ## Deploying ``` python3 utils/compile_bible.py # only when source/ changes ./deploy.sh dev ./deploy.sh prod ``` `deploy.sh` copies what the Dockerfile needs into `.build/` and uploads that with `railway up`; Railway builds it and switches over when the new container is running. Not the repo itself: the CLI skips gitignored paths, which includes `bible/`, and telling it not to would send `.env` and `source/` too. The build logs stream to the terminal, and the dashboard has them as well. Terraform never deploys, and the provider cannot set anything Railway calls config-as-code (healthchecks, start commands); Railway's defaults do. ## State In a bucket on Railway: project `tfstate`, bucket `tfstate` (S3 name `tfstate-9p20m8qvmqf8gqk-z`, endpoint `https://t3.storageapi.dev`, US East). Every project keeps its state there under its own key, `/terraform.tfstate`, with Terraform's lock file next to it. The bucket's keys are `TFSTATE_*` in `.env`; compose passes them to Terraform as `AWS_*`, the names the S3 backend reads. The bucket is made outside Terraform, once: the provider has no bucket resource, and a bucket cannot hold the state that creates it. It was made through the API, since the CLI refuses a workspace token for `init`/`link`: `projectCreate`, then `bucketCreate`, then `environmentPatchCommit` with `{buckets: {: {region: "iad", isCreated: true}}}`. A new project's stack copies the `backend "s3"` block from `railway/main.tf` with a new `key`. If the state is ever lost, nothing on Railway goes away; `terraform import` each resource back (IDs from the dashboard URLs; the Cloudflare records as `/`). ## Cost The state bucket is $0.015 per GB-month: nothing, at a few KB. Hobby plan: $5 a month, which includes $5 of usage. The service idles at a few tens of MB and next to no CPU in each environment ($10 per GB-month of memory, $20 per vCPU-month), so both fit in the included usage. Egress is $0.05/GB, about $0.0001 per new reader (the whole library, ~2.2 MB).