Strapi is a popular open-source headless CMS, but its defaults are tuned for local development — SQLite, local file uploads, and dev mode. Moving to production means switching to a managed database, externalizing media storage, locking down secrets, and building for the right mode. Here's the full path.
Strapi's two big production changes
Two defaults will bite you if you don't change them:
- 1SQLite is the dev default. It doesn't survive container restarts (the file lives in the ephemeral filesystem) and doesn't scale across replicas. Use managed PostgreSQL or MySQL.
- 2Uploads go to the local disk by default. In a container, those files vanish on redeploy. Use an upload provider backed by object storage (S3-compatible).
Database configuration
Point Strapi at your managed database via environment variables. In config/database.ts:
export default ({ env }) => ({
connection: {
client: 'postgres',
connection: {
connectionString: env('DATABASE_URL'),
ssl: env.bool('DATABASE_SSL', false),
},
pool: { min: 2, max: 10 },
},
});Using DATABASE_URL keeps things simple. Set pool.max so total connections across replicas stay under your database limit.
The secret keys Strapi requires
Strapi needs several secrets in production or it won't start (or will be insecure). Generate strong random values for each:
APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=...
ADMIN_JWT_SECRET=...
TRANSFER_TOKEN_SALT=...
JWT_SECRET=...Generate them with openssl rand -base64 32. Store them as secrets in your platform — losing or rotating APP_KEYS invalidates existing sessions, and changing salts can break existing tokens, so set them once and keep them safe.
Media uploads to object storage
Install an S3-compatible upload provider and configure it so media survives redeploys and scales across instances:
npm install @strapi/provider-upload-aws-s3// config/plugins.ts
export default ({ env }) => ({
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
s3Options: {
credentials: {
accessKeyId: env('S3_ACCESS_KEY_ID'),
secretAccessKey: env('S3_SECRET_ACCESS_KEY'),
},
region: env('S3_REGION'),
endpoint: env('S3_ENDPOINT'),
params: { Bucket: env('S3_BUCKET') },
},
},
},
},
});Any S3-compatible object store works.
Building for production
Strapi compiles its admin panel into static assets. Build it as part of your image:
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV NODE_ENV=production
RUN npm run build
FROM node:20-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app ./
EXPOSE 1337
CMD ["npm", "run", "start"]npm run start runs Strapi in production mode (strapi start), which serves the pre-built admin panel and disables the content-type builder (schema changes happen at build time in production, by design).
Deploying on PandaStack
- 1Create a PostgreSQL (or MySQL) database —
DATABASE_URLis injected automatically. - 2Connect your repo as a container app; PandaStack detects the Dockerfile and builds via rootless BuildKit.
- 3Set all required secrets (
APP_KEYS,JWT_SECRET, salts) and your S3 credentials in the dashboard. - 4Push — live build logs stream and you get automatic SSL on your custom domain.
| Concern | Production value |
|---|---|
| Database | Managed PostgreSQL/MySQL |
| Uploads | S3-compatible object storage |
| Mode | strapi start (NODE_ENV=production) |
| Secrets | APP_KEYS, JWT secret, salts |
| Port | 1337 |
Common pitfalls
- Leaving SQLite on — data disappears on redeploy.
- Local file uploads — media vanishes; always use object storage in containers.
- Missing or weak secrets — Strapi refuses to start or becomes insecure.
- Expecting to edit content types in production — the builder is disabled in prod by design; change schemas in dev and redeploy.
- Pool size too large — exceeds the DB connection limit under multiple replicas.
References
- Strapi deployment docs: https://docs.strapi.io/dev-docs/deployment
- Strapi database configuration: https://docs.strapi.io/dev-docs/configurations/database
- Strapi environment & secrets: https://docs.strapi.io/dev-docs/configurations/environment
- Strapi upload provider (S3): https://market.strapi.io/providers/@strapi-provider-upload-aws-s3
- Strapi Docker deployment: https://docs.strapi.io/dev-docs/installation/docker
---
PandaStack's free tier includes a container app and a managed database with DATABASE_URL auto-injected — pair it with object storage and your Strapi CMS is production-ready with automatic SSL. Deploy at https://dashboard.pandastack.io