Back to Blog
Tutorial11 min read2026-07-02

How to Deploy Strapi CMS with a Managed Database

Strapi needs a real database and persistent media storage in production. This guide covers the right database config, secret keys, uploads to object storage, and a clean Docker deployment.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

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:

  1. 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.
  2. 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

  1. 1Create a PostgreSQL (or MySQL) database — DATABASE_URL is injected automatically.
  2. 2Connect your repo as a container app; PandaStack detects the Dockerfile and builds via rootless BuildKit.
  3. 3Set all required secrets (APP_KEYS, JWT_SECRET, salts) and your S3 credentials in the dashboard.
  4. 4Push — live build logs stream and you get automatic SSL on your custom domain.
ConcernProduction value
DatabaseManaged PostgreSQL/MySQL
UploadsS3-compatible object storage
Modestrapi start (NODE_ENV=production)
SecretsAPP_KEYS, JWT secret, salts
Port1337

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

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also