Back to Blog
DevOps

How to Deploy Strapi on Railway with PostgreSQL

Deploy Strapi on Railway with PostgreSQL, persistent uploads, production secrets, verification steps, backups, and clear operational ownership.

Terry Hunt
June 1, 2026
25 min read

This guide shows a manual Strapi deployment on Railway with PostgreSQL and persistent uploads. It avoids a one-click template so you can see which service owns the application, database, secrets, and media.

Railway's current Strapi deployment guide recommends PostgreSQL for the application and either a Railway Volume or an S3-compatible bucket for uploads. The default service filesystem is ephemeral, so an upload stored only inside the application container can disappear during a deployment.

What this deployment includes

  • A Strapi application deployed from a GitHub repository
  • A separate Railway PostgreSQL service
  • Production secrets stored in Railway variables
  • Persistent media through a volume or object storage
  • A public domain, logs, and a documented backup owner

1. Create the Strapi project locally

npx create-strapi@latest my-strapi-app

Select PostgreSQL when the installer asks for a database. Run the project locally, create the initial administrator, and test the content types and permissions before connecting a production database.

Commit the application code to GitHub. Keep .env, database dumps, API tokens, and generated secrets out of the repository.

2. Create the Railway services

  1. Create a Railway project.
  2. Add a PostgreSQL database service.
  3. Add the Strapi application from its GitHub repository.
  4. Confirm the application service's detected build and start commands in the deployment settings.

Railway exposes database variables from the PostgreSQL service. Reference those variables from the Strapi service instead of copying a password into source control.

3. Configure production variables

The exact configuration can change with the Strapi version and project setup. Railway's guide currently uses variables shaped like these:

DATABASE_CLIENT=postgres
DATABASE_HOST=${{Postgres.PGHOST}}
DATABASE_PORT=${{Postgres.PGPORT}}
DATABASE_NAME=${{Postgres.PGDATABASE}}
DATABASE_USERNAME=${{Postgres.PGUSER}}
DATABASE_PASSWORD=${{Postgres.PGPASSWORD}}
DATABASE_SSL=true
NODE_ENV=production

APP_KEYS=<four comma-separated random values>
API_TOKEN_SALT=<random value>
ADMIN_JWT_SECRET=<random value>
JWT_SECRET=<random value>

Generate each secret independently. Do not reuse local development values in production. If your project needs additional transfer, encryption, email, upload-provider, or frontend URL variables, document the owner and rotation process for each one.

4. Make uploaded media persistent

Strapi writes local uploads to public/uploads by default. Railway documents two persistence options:

  • Attach a Railway Volume at /app/public/uploads. This is straightforward, but the volume is tied to one service instance.
  • Configure Strapi's S3 upload provider with a Railway Storage Bucket or another supported object store. This is a better fit when media must be separate from the application container.

Choose the option before editors upload production assets. Moving media later requires an inventory, transfer plan, URL review, and recovery test.

5. Deploy and verify

  1. Trigger a deployment and read the build logs.
  2. Generate a public domain under the application's networking settings.
  3. Open /admin and confirm that the administrator can sign in.
  4. Create and retrieve a test record through the API.
  5. Upload a test image, redeploy, and confirm that the image still exists.
  6. Review public roles and API permissions before connecting the frontend.

6. Plan backups and updates

A running deployment is not a recovery plan. Assign responsibility for PostgreSQL backups, uploaded media, secret rotation, dependency updates, Strapi upgrades, log review, and restore testing. Record the steps needed to rebuild the application if the service or account becomes unavailable.

Common deployment failures

  • The build succeeds but the app cannot start: check the start command, production Node version, database variables, and secret values.
  • The admin loads but content requests fail: review the content type, public or authenticated role permissions, CORS settings, and frontend API URL.
  • Uploads disappear after a redeploy: the application is still writing to the ephemeral filesystem. Attach a volume or configure object storage.
  • The database connection fails: confirm the service references the Railway PostgreSQL variables and uses the correct SSL setting for the deployed environment.

Railway, Strapi Cloud, or another host?

Railway gives you control over the application service, database, variables, and storage choices. That control also gives you responsibility for deployment settings, backups, updates, and incident response. Strapi Cloud packages more of that operational work into a managed service. Compare both against the application's data, recovery, region, scale, and team-ownership requirements rather than choosing from the monthly price alone.

Read the Strapi Cloud vs. self-hosting comparison for that decision, or review Strapi development services if you need help with content models, integrations, deployment, or maintenance.

Related Articles

More articles in DevOps

Enjoyed This Article?

Get more tutorials and insights delivered to your inbox. No spam, just valuable content for web developers.