Skip to content

Deploying a Strapi App

Strapi is an open-source headless CMS built on Node.js that empowers content teams to build flexible, scalable content management systems. It features a customizable admin panel, out-of-the-box REST and GraphQL APIs, content versioning, role-based access control, media management, and extensible plugins. Strapi allows developers to define content types with a visual editor or code, generate APIs automatically, and serve content to any frontend application. It’s ideal for modern JAMstack applications, mobile apps, and multi-channel content distribution.

This comprehensive guide walks you through deploying a Strapi application to Klutch.sh, covering both automatic Nixpacks-based deployments and Docker-based deployments. You’ll learn installation steps, explore sample configurations, set up your database, configure persistent storage for media uploads, manage environment variables, and discover best practices for production deployments.

Table of Contents

  • Prerequisites
  • Getting Started: Install Strapi
  • Sample Code Examples
  • Project Structure
  • Database Configuration
  • Deploying Without a Dockerfile (Nixpacks)
  • Deploying With a Dockerfile
  • Persistent Storage & Media Uploads
  • Environment Variables & Configuration
  • Content Types & API Setup
  • Troubleshooting
  • Resources

Prerequisites

To deploy a Strapi application on Klutch.sh, ensure you have:

  • Node.js 18 or higher - Strapi requires a modern Node.js version
  • npm or yarn - For managing dependencies
  • Git - For version control
  • GitHub account - Klutch.sh integrates with GitHub for continuous deployments
  • PostgreSQL or MySQL database - Strapi requires an external database (SQLite not recommended for production)
  • Klutch.sh account - Sign up for free

Getting Started: Install Strapi

Follow these steps to create and set up a new Strapi application:

  1. Create a new Strapi project using the official create-strapi-app command:
    Terminal window
    npx create-strapi-app@latest my-strapi-app
    cd my-strapi-app

    Choose your database type (PostgreSQL recommended) and preferred package manager (npm or yarn).

  2. Install dependencies (if not already done during creation):
    Terminal window
    npm install
    # or
    yarn install
  3. Create a `.env.example` file with template environment variables for reference:
    Terminal window
    # Database
    DATABASE_CLIENT=postgres
    DATABASE_HOST=localhost
    DATABASE_PORT=5432
    DATABASE_NAME=strapi_dev
    DATABASE_USERNAME=strapi_user
    DATABASE_PASSWORD=your_password
    # Application
    APP_KEYS=key1,key2
    API_TOKEN_SALT=salt_value
    ADMIN_JWT_SECRET=secret_value
    JWT_SECRET=secret_value
  4. Start the development server:
    Terminal window
    npm run develop

    Your Strapi admin panel will be available at http://localhost:1337/admin. You can create your first user and begin defining content types through the visual editor.

  5. Stop the development server when ready, press `Ctrl+C`. Verify your Strapi app builds successfully before deploying to Klutch.sh:
    Terminal window
    npm run build
    npm run start

Sample Code Examples

Creating Content Types Programmatically

Define a custom content type for a blog post:

src/api/post/models/post.js
module.exports = {
attributes: {
title: {
type: 'string',
required: true,
maxlength: 255,
index: true
},
slug: {
type: 'uid',
targetField: 'title',
required: true
},
description: {
type: 'text',
required: true
},
content: {
type: 'richtext',
required: true
},
featuredImage: {
type: 'media',
multiple: false,
required: false
},
author: {
type: 'relation',
relation: 'manyToOne',
target: 'plugin::users-permissions.user'
},
category: {
type: 'relation',
relation: 'manyToOne',
target: 'api::category.category'
},
publishedAt: {
type: 'datetime',
default: null
},
createdAt: {
type: 'datetime',
default: null
}
}
};

Custom Controller with Filtering

src/api/post/controllers/post.js
'use strict';
module.exports = {
async find(ctx) {
const { query } = ctx;
// Custom filtering logic
const filters = {
publishedAt: {
$notNull: true
}
};
const posts = await strapi.entityService.findMany('api::post.post', {
filters,
populate: ['author', 'category', 'featuredImage'],
sort: { publishedAt: 'desc' },
limit: query.limit || 10,
offset: query.offset || 0
});
return posts;
},
async findOne(ctx) {
const { id } = ctx.params;
const post = await strapi.entityService.findOne('api::post.post', id, {
populate: ['author', 'category', 'featuredImage']
});
if (!post) {
return ctx.notFound();
}
return post;
}
};

REST API Query Examples

Once deployed, access your content through REST APIs:

Terminal window
# Get all posts
curl https://example-app.klutch.sh/api/posts?populate=*
# Get single post by ID
curl https://example-app.klutch.sh/api/posts/1?populate=*
# Filter posts by category
curl "https://example-app.klutch.sh/api/posts?filters[category][id][\$eq]=1&populate=*"
# Sort and paginate
curl "https://example-app.klutch.sh/api/posts?sort=publishedAt:desc&pagination[limit]=10&pagination[start]=0"
# GraphQL Query
curl -X POST https://example-app.klutch.sh/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "{ posts(pagination: { limit: 10 }) { data { id title slug content } } }"
}'

Project Structure

A typical Strapi project has this structure:

my-strapi-app/
├── src/
│ ├── api/
│ │ ├── post/
│ │ │ ├── controllers/
│ │ │ │ └── post.js
│ │ │ ├── services/
│ │ │ │ └── post.js
│ │ │ ├── routes/
│ │ │ │ └── post.js
│ │ │ └── models/
│ │ │ └── post.js
│ │ └── category/
│ │ └── ...
│ ├── admin/
│ │ └── app.example.js
│ ├── config/
│ │ ├── api.js
│ │ ├── server.js
│ │ └── database.js
│ ├── plugins/
│ ├── extensions/
│ ├── middlewares/
│ └── index.js
├── public/
│ └── uploads/
├── .env
├── .env.example
├── package.json
└── strapi.config.js

Database Configuration

Strapi requires an external database for production deployments. Configure your database connection before deploying:

PostgreSQL Configuration

For PostgreSQL (recommended):

config/database.js
module.exports = ({ env }) => ({
connection: {
client: 'postgres',
connection: {
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 5432),
database: env('DATABASE_NAME'),
user: env('DATABASE_USERNAME'),
password: env('DATABASE_PASSWORD'),
ssl: env.bool('DATABASE_SSL', true)
},
pool: {
min: 2,
max: 10
},
debug: false
}
});

MySQL Configuration

For MySQL:

config/database.js
module.exports = ({ env }) => ({
connection: {
client: 'mysql',
connection: {
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME'),
user: env('DATABASE_USERNAME'),
password: env('DATABASE_PASSWORD'),
ssl: env.bool('DATABASE_SSL', false)
},
pool: {
min: 2,
max: 10
}
}
});

Deploying Without a Dockerfile

Klutch.sh uses Nixpacks to automatically detect and build your Strapi application. This is the simplest deployment option that requires no additional configuration files.

  1. Test your Strapi app locally to ensure it builds and runs correctly:
    Terminal window
    npm run build
    npm run start
  2. Push your Strapi application to a GitHub repository with all source code, configuration files, and lock files included. Create a `.env.example` file as a reference (do not commit actual secrets).
  3. Log in to your Klutch.sh dashboard.
  4. Create a new project and give it a name (e.g., "My Strapi CMS").
  5. Create a new app with the following configuration:
    • Repository - Select your Strapi GitHub repository and the branch to deploy
    • Traffic Type - Select HTTP (for web applications serving HTTP traffic)
    • Internal Port - Set to 1337 (the default port for Strapi applications)
    • Region - Choose your preferred region for deployment
    • Compute - Select appropriate compute resources (Strapi benefits from adequate memory)
    • Instances - Start with 1 instance for testing, scale as needed
    • Environment Variables - Add all required environment variables (database URL, authentication keys, API tokens, etc.)

    If you need to customize the build or start commands, set these Nixpacks environment variables:

    • BUILD_COMMAND: Override the default build command (e.g., npm run build)
    • START_COMMAND: Override the default start command (e.g., npm run start)
  6. Click "Create" to deploy. Klutch.sh will automatically detect your Node.js/Strapi project, install dependencies, build your application, and start it.
  7. Once deployed, your app will be available at a URL like `example-app.klutch.sh`. Test it by visiting the admin panel at `example-app.klutch.sh/admin` and accessing your REST or GraphQL APIs.

Deploying With a Dockerfile

If you prefer more control over the build and runtime environment, you can use a Dockerfile. Klutch.sh will automatically detect and use any Dockerfile in your repository’s root directory.

  1. Create a `Dockerfile` in your project root:
    # Multi-stage build for optimized Strapi deployment
    FROM node:18-bullseye-slim AS builder
    WORKDIR /app
    # Copy package files
    COPY package*.json ./
    # Install dependencies
    RUN npm ci
    # Copy source code
    COPY . .
    # Build Strapi application
    RUN npm run build
    # Production stage
    FROM node:18-bullseye-slim
    WORKDIR /app
    # Copy package files
    COPY package*.json ./
    # Install production dependencies only
    RUN npm ci --production
    # Copy built application from builder
    COPY --from=builder /app/dist ./dist
    COPY --from=builder /app/public ./public
    # Set environment variables
    ENV NODE_ENV=production
    ENV PORT=1337
    # Expose port
    EXPOSE 1337
    # Health check
    HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD node -e "require('http').get('http://localhost:1337', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
    # Start the application
    CMD ["npm", "run", "start"]
  2. Create a `.dockerignore` file to exclude unnecessary files from the Docker build:
    node_modules
    npm-debug.log
    yarn-error.log
    .git
    .gitignore
    README.md
    .env
    .env.local
    .env.*.local
    .vscode
    .idea
    .DS_Store
    dist
    build
  3. Push your code (with Dockerfile and .dockerignore) to GitHub.
  4. Follow the same deployment steps as the Nixpacks method:
    • Log in to Klutch.sh
    • Create a new project
    • Create a new app pointing to your GitHub repository
    • Set the traffic type to HTTP and internal port to 1337
    • Add all required environment variables for your Strapi instance
    • Click “Create”

    Klutch.sh will automatically detect your Dockerfile and use it to build and deploy your application.

  5. Your deployed app will be available at `example-app.klutch.sh` once the build and deployment complete.

Persistent Storage & Media Uploads

Strapi stores uploaded media files in the /public/uploads directory. For production deployments, attach a persistent volume to preserve uploads:

Adding a Persistent Volume

  1. During app creation in Klutch.sh, or in the app settings, add a persistent volume with: - **Mount Path**: `/app/public/uploads` - **Size**: Choose based on your content (start with 10GB, scale as needed)
  2. Update your Strapi configuration to use the mounted volume:
    config/plugins.js
    module.exports = {
    upload: {
    config: {
    provider: 'local',
    providerOptions: {
    sizeLimit: 10000000 // 10MB per file
    }
    }
    }
    };
  3. For large-scale deployments, consider using cloud storage providers (S3, Cloudinary, Azure Blob Storage) instead of local storage. Configure the appropriate provider in your Strapi plugins.

Cloud Storage Configuration (S3 Example)

config/plugins.js
module.exports = {
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION,
params: {
Bucket: process.env.AWS_BUCKET_NAME
}
}
}
}
};

Environment Variables & Configuration

Strapi applications require specific environment variables for security and configuration. Set these in the Klutch.sh dashboard during app creation or update them afterward.

Required Environment Variables

# Server configuration
PORT=1337
NODE_ENV=production
# Database configuration
DATABASE_CLIENT=postgres
DATABASE_HOST=your-db-host.com
DATABASE_PORT=5432
DATABASE_NAME=strapi_prod
DATABASE_USERNAME=strapi_user
DATABASE_PASSWORD=your_secure_password
DATABASE_SSL=true
# Authentication keys (generate with openssl rand -hex 32)
APP_KEYS=key1_value,key2_value
API_TOKEN_SALT=your_token_salt
ADMIN_JWT_SECRET=your_admin_jwt_secret
JWT_SECRET=your_jwt_secret
# Application URL (for media serving)
PUBLIC_URL=https://example-app.klutch.sh

Optional Environment Variables

# Email configuration
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USERNAME=apikey
SMTP_PASSWORD=your_sendgrid_api_key
SMTP_FROM=noreply@example.com
# Cloud storage (S3)
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=us-east-1
AWS_BUCKET_NAME=your-strapi-bucket
# Logging
LOG_LEVEL=info

Generating Secure Keys

Terminal window
# Generate APP_KEYS (comma-separated)
openssl rand -hex 32 | tr -d '\n' && echo -n "," && openssl rand -hex 32
# Generate API_TOKEN_SALT
openssl rand -hex 32
# Generate ADMIN_JWT_SECRET
openssl rand -hex 32
# Generate JWT_SECRET
openssl rand -hex 32

Content Types & API Setup

Creating a Content Type via Admin Panel

  1. Log in to the admin panel at example-app.klutch.sh/admin
  2. Navigate to Content-Type Builder
  3. Click Create new content-type
  4. Choose Collection Type (for multiple entries like blog posts) or Single Type (for unique pages like About)
  5. Define fields (text, rich text, media, relations, etc.)
  6. Save and your REST/GraphQL API endpoints are generated automatically

Accessing Your APIs

Once content types are created, they’re automatically exposed via:

Terminal window
# REST API
GET /api/posts
POST /api/posts
GET /api/posts/:id
PUT /api/posts/:id
DELETE /api/posts/:id
# GraphQL API
POST /graphql

Troubleshooting

Build Failures

Problem - Deployment fails during the build phase

Solution:

  • Verify your Strapi app builds locally: npm run build
  • Ensure all dependencies are properly listed in package.json
  • Check for TypeScript errors if using TypeScript
  • Verify database configuration in config/database.js
  • Review build logs in the Klutch.sh dashboard for specific errors
  • Ensure all environment variables are set correctly

Database Connection Errors

Problem - App starts but cannot connect to database

Solution:

  • Verify DATABASE_URL or individual database credentials are correct
  • Check database is accessible from Klutch.sh region
  • Ensure database user has proper permissions
  • Test connection locally before deploying
  • For PostgreSQL, verify SSL settings match your database requirements
  • Check database migration status (Strapi runs migrations on startup)
  • Verify database encoding is UTF-8

Upload Storage Issues

Problem - Media uploads fail or uploads disappear after restart

Solution:

  • Verify persistent volume is attached at /app/public/uploads
  • Check volume size is adequate and not full
  • For local storage, monitor volume usage regularly
  • Consider switching to cloud storage (S3, Cloudinary) for better scalability
  • Verify file permissions in the container allow write access
  • Check application logs for storage-related errors

Admin Panel Not Loading

Problem - Admin panel returns 404 or blank page

Solution:

  • Verify Strapi is fully started (check health endpoint: https://example-app.klutch.sh)
  • Clear browser cache and hard refresh (Cmd+Shift+R on Mac, Ctrl+Shift+R on Windows)
  • Check browser console for JavaScript errors
  • Verify ADMIN_JWT_SECRET is set correctly
  • Check application logs for errors
  • Ensure PUBLIC_URL environment variable is set to your deployed domain

Memory Issues

Problem - App crashes with out of memory errors

Solution:

  • Upgrade to a larger compute tier in Klutch.sh
  • Optimize database queries in custom controllers
  • Implement pagination for API responses
  • Monitor memory usage during development
  • Check for memory leaks in custom plugins or middleware
  • Consider using database connection pooling

Performance Issues

Problem - Strapi responses are slow

Solution:

  • Enable query caching with the Cache plugin
  • Optimize database queries (add indexes, use populate wisely)
  • Implement pagination in REST API endpoints
  • Use GraphQL field selection to fetch only needed fields
  • Enable compression in server configuration
  • Consider Redis for caching (requires additional setup)
  • Monitor database query performance

Resources


Summary

Deploying a Strapi headless CMS on Klutch.sh provides a robust, scalable platform for managing content across multiple channels. Whether you choose Nixpacks for simplicity or Docker for complete control, both methods support Strapi’s powerful content management capabilities. With proper database configuration, persistent storage for media, and secure environment variables, your Strapi instance will serve content reliably to web and mobile applications. Strapi’s auto-generated REST and GraphQL APIs, combined with Klutch.sh’s managed infrastructure, enable you to build modern, flexible content systems ready for production workloads.