Railway And Neon Cheat Sheet
Section 16 · Lesson 3 · Level: intermediate · ~12 min · Prereq: Deploy on Railway
Why this matters¶
Railway runs your code; Neon holds your data. Neither is hard on its own, and both are easy to point at the wrong place. Here are the commands, plus the habit that prevents the expensive mistakes: knowing which environment and which branch your next command touches before you run it.
Before any write command: run the status check for the tool you are about to use and say out loud which environment and branch you expect. If you cannot, stop.
railway statusshows the linked environment;neonctl branches listshows every branch you could hit.
Railway¶
A project holds services (each one deployable thing) and environments (isolated sets of services and variables, such as staging and production).
railway login # authenticate once
railway whoami # which account am I acting as
railway link -p <your-project> -e staging -s <your-service> # bind this directory; linking is per directory
railway status # linked project, environment, service
railway list # every project, when you forgot the name
railway service list # services in the current environment
railway service status # deployment status per service, at a glance
railway environment list # environments; --ephemeral shows PR environments
railway environment new pr-123 --copy staging # one environment per pull request
railway up # upload and deploy, streaming build + deploy logs
railway up -d # detached: return now, read logs later
railway up --ci # build logs only, exits when the build ends
railway up --service <your-service> --environment staging # be explicit when a project has several services
railway up --project <your-project> --environment production # --environment is required with --project
railway redeploy --service <your-service> # rebuild the same code, e.g. after a variable change
railway redeploy --from-source # pull the latest commit first
railway down --service <your-service> # remove the most recent deployment
railway logs --service <your-service> # runtime logs
railway logs --service <your-service> -b # build logs: build failure and start failure differ
railway deployment list --service <your-service> # recent deployments, IDs, and SKIPPED ones
railway variable list --service <your-service> --json # contains passwords — never paste the output
railway variable set KEY=value --service <your-service> --skip-deploys # change a variable, no restart
railway variable delete KEY --service <your-service> # retire a rotated credential
railway run <command> # run locally with the linked env's variables injected
railway connect <database-service> # psql into a Railway-hosted Postgres over a tunnel
railway usage # usage and limits, when a bill looks wrong
In CI, replace the interactive login with a project token: RAILWAY_TOKEN=${{ secrets.RAILWAY_TOKEN }} railway up --ci --service <your-service>. Project tokens are scoped to one environment and can only deploy, redeploy and read logs. railway run deserves respect: it injects the linked environment's variables into a local command, which is the easiest way to migrate production by accident.
Neon¶
npx neon@latest init # one-command setup; links a project, writes DATABASE_URL to .env
neonctl auth # authenticate the CLI
neonctl projects list # project IDs; confirm you are pointed at the right one
neonctl link --project-id <your-project> --branch staging # pin project + branch to this directory
neonctl checkout <branch> # change the pin; every later command uses it
neonctl branches list # every branch, and which is the default
neonctl branches create --name staging --parent production # copy-on-write clone with realistic data
neonctl branches set-expiration <branch> --expires-at 2026-12-31T23:59:59Z # PR branches clean themselves up
neonctl branches reset <branch> --parent # throw away a non-production branch's changes
neonctl branches restore <target> <source>@<timestamp> # point a branch at an earlier moment
neonctl branches schema-diff production staging # compare two branches before migrating
neonctl snapshots create --branch production --name before-0007-add-status # a named restore point
neonctl snapshots list # and: neonctl snapshots restore <id>
neonctl connection-string staging # direct endpoint: migrations and admin work
neonctl connection-string staging --pooled # pooled endpoint: application traffic
neonctl psql staging # open psql against that branch
neonctl databases list # and: roles list, operations list, logs, api
The pooled string carries -pooler in its hostname. Direct endpoints can be IPv6-only, so a network with no IPv6 route gives ENETUNREACH; use the pooled endpoint there.
Migrations against a branch¶
neonctl branches list # 1. which branch am I about to change?
export DATABASE_URL="$(neonctl connection-string staging)" # 2. direct string, never the pooler
psql "$DATABASE_URL" -c "select current_database(), now()" # 3. prove the target before writing
python -m app.migrate # 4. your runner; keep the string out of logs
# The rules that make migrations boring
# - schema before code: a route touching a missing column returns 500s on every request
# - idempotent: ADD COLUMN IF NOT EXISTS, CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS
# - schema only: seed data belongs in a script pointed at the staging branch
# - a runner that splits a .sql file on ';' mangles dollar-quoted blocks (DO $$ ... $$),
# so write constraint changes without a DO block or use a real migration tool
Rehearse on a branch of production, not a local database with three rows in it.
The environment matrix¶
Write this table for your project and keep it in the repository.

| Environment | Railway service / environment | Neon branch | Who may write |
|---|---|---|---|
| Local dev | your process on your laptop | dev-<you>, throwaway |
you |
| Staging | api-staging in environment staging |
staging |
CI, on merge to main |
| Production | api in environment production |
production, the default branch |
a gated promotion, human-approved |
flowchart TD
subgraph L["local dev"]
LENV["environment: local"] --> LSVC["service: process on your laptop"]
LENV --> LDB["branch: dev-you, throwaway"]
end
subgraph S["staging"]
SENV["environment: staging"] --> SSVC["service: api-staging"]
SENV --> SDB["branch: staging"]
end
subgraph P["production"]
PENV["environment: production"] --> PSVC["service: api"]
PENV --> PDB["branch: production, the default"]
end
LDB --> SDB
SDB --> PDB
Each Railway environment carries its own DATABASE_URL, and CI holds one secret per environment (DATABASE_URL_STAGING, DATABASE_URL_PRODUCTION). The pairing rule is absolute: the service in environment X reads the string whose branch is X's database. If a staging service's DATABASE_URL names the production branch, staging writes to production and nothing in the UI warns you. Branching is what makes the pairing safe — a delete on staging cannot reach production, because only changed pages get duplicated.
Safety habits before any write¶
- Check the link, then write.
railway statusprints the environment this directory is bound to. A stale link is the usual reason a deploy lands somewhere unexpected. - Check the pin, then write.
neonctl checkoutpersists: if your last session pinnedproduction, a bareneonctl psqlopens production. - Prove the target with one query.
select current_database()against the string you are about to use costs nothing. - Never write to production from a laptop. Production migrations belong in the gated promotion path, after staging verified the change.
- Snapshot before risky work.
neonctl snapshots create --branch production --name before-0007is cheap; having no restore point mid-incident is not. - Restore sideways. Restore into a new branch, verify, then repoint. Restoring over a live branch extends the outage you were fixing.
- One deploy path per service. A repo trigger plus a pipeline
railway updeploys twice, and the autodeploy can race your migration step.
Try it¶
- Create a throwaway project,
railway linkit, confirm withrailway status. - Deploy a server with a
/healthroute (railway up --ci), then readrailway logs. - Set a variable with
--skip-deploysand confirm the logs show no restart. - Create
staging, then compareneonctl connection-string stagingwith its--pooledform. - Run
CREATE TABLE IF NOT EXISTS demo (id int)twice againststaging; the second run must succeed. - Snapshot the default branch, list snapshots, then write your environment matrix into
docs/environments.md.
Common mistakes¶
- Deploying to a linked environment you forgot about. The link is per directory and survives closed terminals.
- Changing a variable without
--skip-deploys. One edit restarts the service mid-rollout. - Using the pooled string for migrations. Pooling multiplexes short-lived clients; admin work belongs on the direct endpoint.
- Migrating production without rehearsing on a branch. Meet the
NOT NULLcolumn existing rows cannot satisfy before your users do. - Non-idempotent migrations.
ADD COLUMNwithoutIF NOT EXISTSfails the second time and hides the real error behind a duplicate-column message. - Treating a skipped deploy as a failure. Unchanged code yields a SKIPPED deployment, no build logs, and a non-zero CLI exit. Read the message first.
- Printing variables into a chat or ticket. The
--jsonvariable list includes credentials; share the hostname.
Key takeaways¶
- Project > service > environment on Railway; project > branch on Neon. Say which one you mean every time.
railway statusandneonctl branches listare the two commands that stop wrong-target writes.- Direct endpoint for migrations, pooled endpoint for app traffic, one
select current_database()to prove it. - Migrate before the code that needs the schema, idempotently, and rehearse on a branch first.
- Snapshot before risky work; restore into a new branch rather than over a live one.
- One deploy path per service; production changes only through the gated promotion.
Further learning¶
- Deploying with the CLI — deploy modes, project tokens, redeploy and PR environments.
- Railway CLI reference and deploying with the CLI — every command, plus deploy modes and project tokens.
- Railway docs — the root to search for environments, services and variables.
- Neon documentation — branching, pooling, snapshots, restore and current limits.
- Deploy on Railway and Postgres on Neon — the lessons behind this sheet.
- Environments and promotion and Rollbacks and incidents — the process around these commands.