Deploy Strapi with DeployHQ
Strapi is an open-source, Node.js-based headless CMS that gives developers full control over their content architecture. It exposes both REST and GraphQL APIs out of the box, supports custom content types through its admin panel, and lets you choose your own database, media storage, and frontend framework. That flexibility makes it a strong fit for projects ranging from marketing sites to mobile app backends.
This guide walks through deploying a Strapi project to a production server using DeployHQ for automated, repeatable releases.
Prerequisites
- A Strapi 4.x or 5.x project in a Git repository (GitHub, GitLab, or Bitbucket)
- A Linux server (Ubuntu/Debian recommended) with SSH access
- Node.js 18+ and npm installed on the server
- A production database — PostgreSQL or MySQL (Strapi uses SQLite by default locally, but SQLite is not recommended for production)
- PM2 installed globally on the server (
npm install -g pm2)
Strapi Project Structure
Before configuring deployment, it helps to understand what gets deployed:
my-strapi-project/
├── config/ # Server, database, plugin, and middleware config
│ ├── database.js # Database connection settings
│ ├── server.js # Host, port, app keys
│ ├── middlewares.js # Middleware stack
│ └── env/
│ └── production/ # Production-specific overrides
│ ├── database.js
│ └── server.js
├── src/
│ ├── api/ # Custom content types, controllers, routes
│ ├── components/ # Shared reusable components
│ └── plugins/ # Custom plugins
├── public/
│ └── uploads/ # User-uploaded media (local provider)
├── database/
│ └── migrations/ # Database migration files
├── .env # Environment variables (never committed)
├── package.json
└── package-lock.json
The key directories for deployment are config/env/production/ (production-specific settings), src/ (your application logic), and public/uploads/ (persistent media that must survive between releases).
Database Configuration for Production
Strapi defaults to SQLite for local development, but production deployments should use PostgreSQL or MySQL for reliability, concurrent access, and backups.
Create a production database configuration at config/env/production/database.js:
module.exports = ({ env }) => ({
connection: {
client: 'postgres',
connection: {
host: env('DATABASE_HOST', '127.0.0.1'),
port: env.int('DATABASE_PORT', 5432),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD'),
ssl: env.bool('DATABASE_SSL', false) && {
rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
},
},
pool: {
min: env.int('DATABASE_POOL_MIN', 2),
max: env.int('DATABASE_POOL_MAX', 10),
},
},
});
Install the appropriate database driver in your project before deploying:
# For PostgreSQL
npm install pg
# For MySQL
npm install mysql2
Environment Configuration
Strapi reads environment variables from .env and supports environment-specific configuration through the config/env/ directory. For production, you need at minimum:
HOST=0.0.0.0
PORT=1337
APP_KEYS=generate-four-random-base64-keys,comma-separated
API_TOKEN_SALT=generate-random-string
ADMIN_JWT_SECRET=generate-random-string
TRANSFER_TOKEN_SALT=generate-random-string
JWT_SECRET=generate-random-string
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=strapi_production
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=your-secure-password
NODE_ENV=production
Generate the secret keys with openssl rand -base64 32 for each value. Never reuse keys between environments.
Media Uploads and Storage Providers
By default, Strapi stores uploaded files to public/uploads/ on disk. This works but introduces a deployment consideration: the uploads directory must persist between releases. For zero-downtime deployments, you will configure this as a shared path in DeployHQ.
For production at scale, consider an external provider:
# AWS S3
npm install @strapi/provider-upload-aws-s3
# Cloudinary
npm install @strapi/provider-upload-cloudinary
Configure your chosen provider in config/env/production/plugins.js. External storage eliminates shared-path complexity entirely and keeps your deployment stateless.
DeployHQ Setup
Create a Project
Sign up at DeployHQ and create a new project. Connect your Git repository — DeployHQ supports GitHub, GitLab, and Bitbucket. For connection issues, consult the support documentation.
Configure the Server
Go to Servers > New Server:
- Name the server and select SSH as the protocol
- Enter your server hostname or IP address
- Create a dedicated deployment user on your server:
sudo adduser deployhq
sudo usermod -a -G www-data deployhq
su - deployhq
mkdir -p ~/.ssh && chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
- Paste the DeployHQ public key, save, and set permissions:
chmod 600 ~/.ssh/authorized_keys
- Set the Deployment Path to your application directory (e.g.
/var/www/strapi-app) - Enable Perform zero-downtime deployments on this server — this creates
current,releases, andshareddirectories, so each deploy is atomic with instant rollback capability - Set Environment to
productionand enable Automatically deploy on push
Shared Paths for Persistent Data
When zero-downtime deployments are enabled, each release gets its own directory. Files that must persist across releases — like user uploads — need to be configured as shared paths.
Add this as a shared directory:
public/uploads— preserves uploaded media between releases
DeployHQ symlinks these paths from the shared/ directory into each new release automatically.
Config Files
Go to Config Files > New Config File and create a .env file. Paste your production environment variables. DeployHQ injects this file into every release, keeping secrets out of your repository.
Build Pipeline
Strapi needs to install dependencies and build the admin panel on each deploy. You can use .deploybuild.yaml to run these steps in DeployHQ's build environment before files are transferred to your server:
build:
- npm ci --production=false
- NODE_ENV=production npm run build
Using npm ci --production=false ensures devDependencies (required for the build step) are installed. The npm run build command compiles the Strapi admin panel — this is a separate React application that gets built into the build/ directory.
Post-Deploy SSH Commands
Go to SSH Commands > New SSH Command and create these commands in order:
Install production dependencies:
cd %path% && npm ci --omit=dev
Restart the application with PM2:
pm2 restart strapi --update-env || pm2 start npm --name strapi -- start
The fallback pm2 start handles the first deployment when no PM2 process exists yet. The --update-env flag ensures PM2 picks up any changed environment variables.
PM2 Process Management
PM2 keeps your Strapi instance running, restarts it on crashes, and manages log rotation. Create an ecosystem.config.js in your project root:
module.exports = {
apps: [
{
name: 'strapi',
cwd: '/var/www/strapi-app/current',
script: 'npm',
args: 'start',
env: {
NODE_ENV: 'production',
},
instances: 1,
autorestart: true,
max_memory_restart: '1G',
},
],
};
After the first deployment, start the process with:
pm2 start ecosystem.config.js
pm2 save
pm2 startup
The pm2 startup command generates a system service so Strapi restarts automatically if the server reboots.
Node.js Version Management
Strapi 5.x requires Node.js 18 or 20 (LTS versions). Use a version manager like nvm on your server to lock the Node version:
nvm install 20
nvm alias default 20
Add an .nvmrc file to your repository root so the correct version is documented:
20
Health Checks and Monitoring
Strapi exposes a /_health endpoint that returns a 204 status when the server is running. Use this for uptime monitoring:
curl -sf http://localhost:1337/_health && echo "OK" || echo "DOWN"
Combine this with PM2's monitoring (pm2 monit) to track memory usage, CPU, and restart counts. For production, set up an external monitoring service that polls the health endpoint and alerts on downtime.
Troubleshooting Common Issues
Admin panel shows a blank page after deploy — The admin build is missing or outdated. Verify npm run build ran successfully during deployment. Check that build/ is not in your .gitignore if you are building locally, or that the build pipeline completed without errors.
Database connection refused — Confirm the database credentials in your .env match the production database. Check that PostgreSQL or MySQL is running and accepting connections on the configured host and port. If using SSL, verify the certificate settings.
Uploads disappear after deploy — The public/uploads directory is not configured as a shared path. With zero-downtime deployments, each release gets a fresh directory tree. Add public/uploads to shared paths in DeployHQ's server settings.
Out of memory during build — The Strapi admin build (a React webpack build) can consume significant memory. If your server has limited RAM, build in DeployHQ's build pipeline rather than on the server. Alternatively, increase the Node memory limit: NODE_OPTIONS=--max-old-space-size=2048 npm run build.
PM2 process not found — If PM2 cannot find the strapi process after the first deploy, the initial SSH command should use the || fallback pattern shown above. Subsequent deploys will find the existing process and restart it.
DeployHQ automates the entire Strapi deployment pipeline — from building the admin panel to restarting the production server — so every push to your repository triggers a consistent, repeatable release. Start your free trial and connect your Strapi project in minutes.
For more deployment guides covering other frameworks and platforms, browse the full guides library. If you run into issues, reach out at support@deployhq.com or on Twitter/X.