all posts

The best Strapi hosting platforms in 2026

Ajay Kumar··10 min read

Strapi looks like a Node app, and it is, but it is a Node app with four separate requirements that most tutorials mention individually and never together. Miss one and the failure is specific enough to be diagnosable, which is the good news; the bad news is that you usually diagnose it in production, on a Friday, after someone's images disappeared.

This guide is organised around those four requirements, because they eliminate more platforms than any feature comparison does.

The four things Strapi needs

  • A long-running Node process. The admin panel, the content API, webhooks, and lifecycle hooks all assume a server that stays alive. This is not a serverless workload and attempts to make it one go badly.
  • A real database. SQLite is the default and is fine for local development. In production it puts your entire content store on a filesystem that most platforms wipe on deploy.
  • Durable storage for uploads. Strapi's default upload provider writes to a local public/uploads directory. On any ephemeral filesystem, every image an editor uploaded is gone at the next deploy — and the database still has rows pointing at them, so you get broken images rather than an error.
  • A build step. The admin panel is a React application compiled at build time, and it needs your production environment variables during that build. An app that boots without a rebuilt admin panel serves a blank page.
The uploads one is the expensive mistake, because it is silent and it is discovered late. Nothing errors at deploy time. The site simply starts showing broken images for content added since the last deploy, and by the time anyone notices, several deploys of uploads are unrecoverable. Configure an S3-compatible upload provider before you let anyone use the CMS.

The environment that makes or breaks a deploy

Strapi's secrets are more interesting than most, because losing them is not merely a login problem. APP_KEYS signs sessions. API_TOKEN_SALT hashes API tokens. Regenerate these on each boot — which is the default if you do not set them — and every existing API token stops working and everyone is logged out, on every deploy.

# Generate once, store in your platform's secret store, never regenerate.
APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=...
ADMIN_JWT_SECRET=...
TRANSFER_TOKEN_SALT=...
JWT_SECRET=...

DATABASE_CLIENT=postgres
DATABASE_URL=postgres://user:pass@host:5432/strapi
DATABASE_SSL=true

# Needed at BUILD time, not just runtime -- the admin panel bakes it in.
STRAPI_ADMIN_BACKEND_URL=https://cms.example.com

NODE_ENV=production
HOST=0.0.0.0        # not localhost, or health checks never connect
PORT=1337

That build-time note is the second most common deploy failure. STRAPI_ADMIN_BACKEND_URL is compiled into the admin bundle, so a platform that only exposes environment variables at runtime produces an admin panel that tries to call the wrong origin. The symptom is an admin login page that loads and then fails every request.

Uploads: use a provider, not the disk

Switching Strapi's upload provider is a config file and a package, and it is the highest-value fifteen minutes in a Strapi deployment. Once uploads go to object storage, your application container becomes genuinely stateless and you can redeploy, scale, or move hosts without thinking about it.

// config/plugins.js -- S3-compatible storage (S3, R2, Spaces, MinIO)
module.exports = ({ env }) => ({
  upload: {
    config: {
      provider: "aws-s3",
      providerOptions: {
        s3Options: {
          credentials: {
            accessKeyId: env("S3_ACCESS_KEY_ID"),
            secretAccessKey: env("S3_ACCESS_SECRET"),
          },
          region: env("S3_REGION"),
          endpoint: env("S3_ENDPOINT"),   // set for R2/Spaces/MinIO
          params: { Bucket: env("S3_BUCKET") },
        },
      },
    },
  },
});

// Then widen the CSP in config/middlewares.js, or the admin panel's
// image previews are blocked and you will assume the upload failed.

The alternative — a persistent volume mounted at public/uploads — is legitimate and simpler if you are on a single instance and want files on a normal filesystem. It just means the app is pinned to one machine and one disk, which is a trade rather than a mistake.

The platforms

  • Strapi Cloud — The first-party option. Database, uploads, and builds handled, and the environment secrets are not something you can lose. You give up filesystem access and some control over the build, which matters if you have custom plugins with native dependencies.
  • Render — A long-running web service, managed Postgres next door, a persistent disk if you want one, and environment variables available at build time. Among the least surprising ways to run Strapi.
  • Railway — Fast path from repo to a running CMS with a database attached, and per-environment variables that are easy to reason about. Popular for Strapi specifically.
  • DigitalOcean App Platform — Managed deploy plus managed Postgres and Spaces for uploads, all from one vendor. Check the build container's memory: the admin panel build is the step that runs out of RAM on small instances.
  • Fly.io — A real VM with volumes if you want local uploads, and easy placement near your database. Good when you want control without a server to patch.
  • A VPS with Docker Compose — Strapi, Postgres, and a reverse proxy on one box. Cheapest, most control, and you own backups and TLS. Perfectly reasonable for a small site if the backups actually happen.
  • Heroku — Works, with the classic caveat: the filesystem is ephemeral, so the uploads provider is not optional here, it is mandatory.
  • PandaStack — Git-driven with no Dockerfile: it detects the Node build, runs it with your environment available at build time so STRAPI_ADMIN_BACKEND_URL is baked correctly, then starts the server with PORT and HOST injected. Each app is a Firecracker microVM with 4 GiB of RAM, which is the headroom the admin-panel build wants, and managed Postgres sits alongside with point-in-time recovery. Persistent volumes are available if you prefer local uploads to object storage. Best when you want the CMS, its database, and its backups on one platform; not the pick if you want a one-click CMS template.

The admin build is where deploys actually fail

Strapi's runtime footprint is modest. Its build footprint is not — compiling the admin panel is a Vite build over a large dependency tree, and it is routinely the step that gets killed on a 512 MB or 1 GB build container. The error is usually an out-of-memory kill that surfaces as a build exiting non-zero with no obvious cause.

So when you compare platforms, compare the memory available during the build, not just at runtime. Some hosts build in a smaller container than they run in, which produces the confusing situation where your app would run fine if only it could finish building.

Content types, migrations, and why staging is different

One structural thing about Strapi that shapes your workflow: content types are defined in code, but the data lives in the database. Change a content type and Strapi alters the schema on startup. That is convenient in development and worth being careful with in production, particularly for changes that drop or rename fields.

The practical habit is to test schema changes against a copy of production data rather than an empty staging database, because the failures that matter — a required field added to a table that already has rows, a type change that will not cast — only appear when there is data. A database clone per branch makes this cheap enough to actually do.

Pick by situation

  • You want a CMS, not an infrastructure project → Strapi Cloud, unless you need custom plugins with native dependencies.
  • Small site, one instance, cost matters → a VPS with Docker Compose, or Render's smallest tier, with a persistent disk for uploads and real database backups.
  • Team site with editors uploading media daily → object storage for uploads, managed Postgres with point-in-time recovery, and a host with build-time environment variables.
  • You also host the frontend that consumes the API → whichever platform runs both, so the CMS and the site share a network and a bill.
  • Content model changes frequently → a host where a database branch per pull request is cheap. Testing migrations on empty staging data teaches you nothing.

The short version

Strapi is not difficult to host, but it is unforgiving about four things: it needs a process that stays alive, a database that is not SQLite, uploads that do not live on the container's disk, and secrets that do not change between deploys.

Get those right and any platform on this list will run it for a year without attention. Get the uploads one wrong and you will find out months later, when the images are already gone.

Frequently asked questions

Can I run Strapi on serverless or edge functions?

No, and it is not a limitation worth fighting. Strapi is a long-running Node server: it serves an interactive admin panel, keeps database connection pools alive, runs lifecycle hooks and cron tasks, and expects to be the same process across requests. A function platform freezes or discards your process shortly after a response, which breaks the connection pooling immediately and the admin experience shortly after, and the admin panel's build output needs to be served by the same application anyway. What people sometimes mean by this question is whether the frontend consuming Strapi can be serverless or static, and that is entirely fine — a statically generated site or an edge-rendered frontend calling the Strapi content API is a common and sensible architecture. It is only the CMS itself that needs a server.

Why did my Strapi uploads disappear after deploying?

Because the default upload provider writes to a local public/uploads directory, and on most platforms that directory belongs to a container replaced on every deploy. The files go with the old container while the database rows still reference them, which is why the symptom is broken images rather than an error — nothing failed, the bytes are simply not there any more. Uploads from before your last deploy are usually unrecoverable, so the priority is stopping the bleeding: configure an S3-compatible upload provider so new files go to object storage, or attach a persistent volume mounted at the uploads path if you are on a single instance and prefer a normal filesystem. Do this before editors start using the CMS in earnest, because every deploy in between is another batch of media lost silently.

Should I use SQLite or Postgres for Strapi in production?

Postgres, in essentially every production case. SQLite is an excellent default for local development because it needs no setup, and that convenience is exactly what makes it dangerous in production — it puts your entire content store in a file on the application container's disk, so an ephemeral filesystem loses everything on deploy, backups mean copying a file nobody thought to copy, and you cannot run a second instance at all. Switching later is possible but means an export and import with the attendant risk to live content, whereas starting on Postgres costs one environment variable and a connection string. Use a managed Postgres with automated backups and point-in-time recovery so restoring to a specific moment is a supported operation rather than an improvisation.

Why does my Strapi admin panel show a blank page in production?

Almost always the admin build rather than the runtime. Two causes account for most of it. First, STRAPI_ADMIN_BACKEND_URL was not available at build time — it is compiled into the admin bundle, so if your platform only exposes environment variables at runtime, the bundle points at the wrong origin and every admin request fails after the page loads. Second, the admin build did not actually complete: compiling the panel is memory-hungry and gets killed on small build containers, which surfaces as a build failure with an unhelpful message or, worse, a deploy that proceeds with stale or missing assets. Check your build logs for an out-of-memory kill first, then confirm the backend URL was set during the build and not only in the runtime environment. Content-security-policy misconfiguration is a distant third and usually shows broken images rather than a blank page.

How do I test Strapi content-type changes safely?

Against a copy of real production data, not an empty staging database. Strapi applies content-type changes to the schema when the application starts, which is smooth on an empty database and where the interesting failures live when there are rows: adding a required field to a populated table, changing a type in a way that will not cast, or removing a field that something still references. None of those appear in a clean environment, so a staging database that has never held real content gives you false confidence. The workflow that works is a database clone per branch — copy-on-write branching makes this fast and cheap enough to do routinely — where you run the new content types against representative data, confirm the schema change succeeds, and check that existing entries survive it before the change reaches production.

Keep reading

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.