---
title: "Deploy Strapi"
description: "Content management system with an editor and an API for any site"
category: "CMS"
url: https://railway.com/deploy/strapi-cms
---

# Deploy Strapi

Content management system with an editor and an API for any site

**[Deploy Strapi on Railway](https://railway.com/template/strapi-cms)**

- **Creator:** A3A
- **Category:** CMS
- **Total deploys:** 1

## Template content

### Postgres https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/postgres.svg

- **Image:** ghcr.io/railwayapp-templates/postgres-ssl:18

### strapi https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/strapi.svg

- **Source:** https://github.com/gridalpha/strapi-railway
- **Public domain:** Yes

## Buckets

- **strapi-media**

## Documentation

![Strapi logo](https://cmscritic.com/ms-content/uploads/2021/04/jpg.jpg?format=auto&amp;width=2048)

# Deploy and Host Strapi on Railway

Strapi is an open-source headless CMS built on Node.js. You model content as structured types — articles, products, landing pages — and Strapi gives editors a polished admin panel plus an automatically generated REST and GraphQL API to read them from any front end. Teams reach for it when a traditional CMS forces its templating on them but a bare database is too little. It powers Next.js marketing sites, mobile back ends, e-commerce catalogues and documentation portals.

Self-host Strapi on Railway and the template wires the pieces together. The `strapi` service builds from the [gridalpha/strapi-railway](https://github.com/gridalpha/strapi-railway) repository, whose Dockerfile compiles the admin panel at build time, so containers start in seconds. A managed **Postgres** service stores content, users and permissions, and a Railway **object storage bucket** holds media uploads, so the container keeps no durable state. Postgres and the bucket stay private.

![Diagram of the Strapi and Postgres services on Railway](https://res.cloudinary.com/rroe4rtk/image/upload/v1787225700/strapi-architecture.png)

## Getting Started with Strapi on Railway

Once the deploy goes green, open the public URL and add `/admin`. Strapi ships **no default credentials** — the first visit shows a registration form creating the super administrator, and it stays open until somebody claims it, so register immediately after deploying. The password needs at least eight characters with an uppercase letter, a lowercase letter and a number.

You land on the dashboard. Click **Content Manager** and you will find an example `Article` type with title, slug, excerpt, rich-text body and cover image. Create an entry, press the refresh icon beside the slug to generate one, then hit **Publish**. Open **Media Library** and upload an image — if the thumbnail renders, your bucket and credentials are correct, because it is served straight from object storage.

To confirm the API, go to **Settings → Users &amp; Permissions → Roles → Public**, tick `find` and `findOne` for Article and save. Fetching `/api/articles?populate=cover` now returns the entry as JSON with its cover — one request proving database, API and storage are all wired.

![Strapi Media Library showing an image stored on object storage](https://res.cloudinary.com/rroe4rtk/image/upload/v1787225705/strapi-media-library.png)
![Strapi Content Manager listing a published Article entry](https://res.cloudinary.com/rroe4rtk/image/upload/v1787225709/strapi-content-manager.png)
![Strapi editor with a published article's title, excerpt and body](https://res.cloudinary.com/rroe4rtk/image/upload/v1787225714/strapi-article-editor.png)

## About Hosting Strapi

Strapi separates content management from delivery. Editors work in the admin panel; your front end consumes JSON. Nothing about presentation is baked in, so the same content can drive a website, an iOS app and a digital sign at once. Self-hosting keeps the database, the media and the API in infrastructure you control, with no per-seat pricing.

Key capabilities:

- **Content-Type Builder** — collection and single types with relations, components and dynamic zones
- **Auto-generated REST and GraphQL APIs** with filtering, sorting, pagination and deep population
- **Draft and publish** plus internationalisation for multi-locale content
- **Role-based access control** for admins and API consumers, with scoped tokens
- **Media library** with responsive image variants on upload
- **Plugin system** and lifecycle hooks for custom logic

The `strapi` container runs the admin panel and API. **Postgres** is the system of record — every entry, user, role and API token lives there, which is why the template provisions it instead of SQLite on ephemeral disk. The **object storage bucket** holds uploads, so a redeploy replaces the container without losing an image.

## Why Deploy Strapi on Railway

- Postgres provisioned and connected by reference — no connection strings to copy
- Object storage attached, so uploads survive every redeploy
- Builds straight from the source repository on every push
- TLS, public domain and private networking configured automatically
- Health checks and restart policies already set

## Common Use Cases

- **Marketing sites and blogs** — editors publish in Strapi while Next.js, Nuxt or Astro renders statically
- **Mobile and web app back ends** — one content API serving iOS, Android and web from a single model
- **E-commerce catalogues** — products, variants, categories and imagery consumed by a storefront
- **Multi-locale content hubs** — locale variants with role-scoped teams

## Dependencies for Strapi

- **Strapi 5** (`@strapi/strapi`), built from [gridalpha/strapi-railway](https://github.com/gridalpha/strapi-railway) — application and admin panel
- **PostgreSQL 18** (`ghcr.io/railwayapp-templates/postgres-ssl:18`) — all content, users and permissions
- **Railway object storage bucket** — media uploads and their generated image variants

### Environment Variables Reference

| Variable | Purpose |
|---|---|
| `APP_KEYS` | Comma-separated session signing keys |
| `ADMIN_JWT_SECRET`, `JWT_SECRET` | Sign admin sessions and API tokens |
| `API_TOKEN_SALT`, `TRANSFER_TOKEN_SALT` | Salt generated tokens |
| `ENCRYPTION_KEY` | Encrypts sensitive values at rest |
| `DATABASE_URL` | Postgres connection string |
| `AWS_BUCKET`, `AWS_ENDPOINT`, `AWS_REGION` | Object storage location |
| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | Object storage credentials |
| `AWS_SIGNED_URL_EXPIRES` | Media link lifetime in seconds |
| `PUBLIC_URL` | Override canonical URL for a custom domain |

The six secrets are generated at deploy time. **Do not rotate them on a running instance** — changing `ENCRYPTION_KEY` makes encrypted values unreadable, and changing the JWT secrets invalidates every admin session and API token.

### Deployment Dependencies

- [Strapi docs](https://docs.strapi.io) · [Strapi on GitHub](https://github.com/strapi/strapi)
- [Source repository](https://github.com/gridalpha/strapi-railway)
- Node.js 22 runtime, PostgreSQL 18

## Hardware Requirements for Self-Hosting Strapi

| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU | 2 vCPU |
| RAM | 1 GB | 2 GB |
| Storage | Database only (media on object storage) | 10 GB Postgres volume |
| Runtime | Node.js 22, PostgreSQL 14+ | Node.js 22, PostgreSQL 18 |

## Self-Hosting Strapi

Content types in Strapi are code, not database rows. The Content-Type Builder is disabled when `NODE_ENV=production`, because adding a type writes files and needs a restart. Model locally and ship through git — clone the repository and run it against SQLite:

```
git clone https://github.com/gridalpha/strapi-railway
cd strapi-railway
npm install
cp .env.example .env
npm run develop
```

That opens the admin on `http://localhost:1337/admin` with the Content-Type Builder available. Build your types, commit the generated files and push — Railway rebuilds and they appear in the Content Manager:

```
git add src/api
git commit -m "add content types"
git push
```

To run the production image locally, build the repository's Dockerfile and pass the same variables the template sets — `DATABASE_CLIENT`, `DATABASE_URL`, `APP_KEYS` and the five remaining secrets.

## Is Strapi Free to Self-Host?

Strapi's Community Edition is free and open source, with no cap on content types, entries, API calls or admin users. Paid tiers add single sign-on, audit logs and release management — none of which a production site requires. Self-hosting on Railway costs infrastructure only: the container, Postgres and your media storage, typically a few dollars a month for a small site.

## FAQ

**What is Strapi?**
An open-source headless CMS on Node.js. It gives editors an admin panel for structured content and developers an automatically generated REST and GraphQL API, leaving the front end to you.

**What does this Railway template deploy?**
The Strapi 5 app built from a production Dockerfile, a managed PostgreSQL 18 database, and an object storage bucket for media. Secrets are generated at deploy time and services connected by reference.

**Why does the template include Postgres instead of SQLite?**
SQLite would live on the container filesystem, replaced on every deploy, so content would disappear. Postgres is what Strapi recommends for production and makes concurrent editing safe.

**Where do uploaded images go, and why do their URLs contain a signature?**
Media goes to the object storage bucket, not container disk, so it survives redeploys. The bucket is private, so Strapi serves each file through a time-limited signed URL — including to anonymous readers of the public API. Links last seven days; adjust `AWS_SIGNED_URL_EXPIRES` if your build caches them longer.

**How do I create content types on a deployed instance?**
Not in production — the Content-Type Builder is disabled when `NODE_ENV=production` because it writes files and needs a restart. Clone the repository, run `npm run develop`, model your types, then commit `src/api` and push. Railway rebuilds and they appear.

**How do I connect a Next.js or Nuxt front end?**
Enable `find` and `findOne` under Settings → Users &amp; Permissions → Roles → Public, or create a scoped token under Settings → API Tokens and send it as a bearer token. Then fetch `/api/?populate=*`.

**Can I run more than one instance of Strapi?**
The template ships one replica. Media lives in object storage and state in Postgres, so the app is stateless — but Strapi runs scheduled jobs in-process without leader election, so anything in `config/cron.js` runs once per replica. Handle that before scaling out.


## Similar templates

- [Libredesk - Complete Setup](https://railway.com/deploy/libredesk-complete-setup) — Complete self-hosted omnichannel customer support desk.
- [Paperless-ngx](https://railway.com/deploy/paperless-ngx-3) — Paperless-ngx — document management with OCR and full-text search
- [Instatic CMS - Postgres](https://railway.com/deploy/instatic-cms-postgres) — Design, build and manage powerful static sites from state-of-the-art CMS

Open this page in a browser: https://railway.com/deploy/strapi-cms
