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:
-
Create a new Strapi project using the official create-strapi-app command:
Terminal window npx create-strapi-app@latest my-strapi-appcd my-strapi-appChoose your database type (PostgreSQL recommended) and preferred package manager (npm or yarn).
-
Install dependencies (if not already done during creation):
Terminal window npm install# oryarn install -
Create a `.env.example` file with template environment variables for reference:
Terminal window # DatabaseDATABASE_CLIENT=postgresDATABASE_HOST=localhostDATABASE_PORT=5432DATABASE_NAME=strapi_devDATABASE_USERNAME=strapi_userDATABASE_PASSWORD=your_password# ApplicationAPP_KEYS=key1,key2API_TOKEN_SALT=salt_valueADMIN_JWT_SECRET=secret_valueJWT_SECRET=secret_value -
Start the development server:
Terminal window npm run developYour 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.
-
Stop the development server when ready, press `Ctrl+C`. Verify your Strapi app builds successfully before deploying to Klutch.sh:
Terminal window npm run buildnpm run start
Sample Code Examples
Creating Content Types Programmatically
Define a custom content type for a blog post:
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
'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:
# Get all postscurl https://example-app.klutch.sh/api/posts?populate=*
# Get single post by IDcurl https://example-app.klutch.sh/api/posts/1?populate=*
# Filter posts by categorycurl "https://example-app.klutch.sh/api/posts?filters[category][id][\$eq]=1&populate=*"
# Sort and paginatecurl "https://example-app.klutch.sh/api/posts?sort=publishedAt:desc&pagination[limit]=10&pagination[start]=0"
# GraphQL Querycurl -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.jsDatabase Configuration
Strapi requires an external database for production deployments. Configure your database connection before deploying:
PostgreSQL Configuration
For PostgreSQL (recommended):
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:
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.
-
Test your Strapi app locally to ensure it builds and runs correctly:
Terminal window npm run buildnpm run start - 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).
- Log in to your Klutch.sh dashboard.
- Create a new project and give it a name (e.g., "My Strapi CMS").
-
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)
- Click "Create" to deploy. Klutch.sh will automatically detect your Node.js/Strapi project, install dependencies, build your application, and start it.
- 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.
-
Create a `Dockerfile` in your project root:
# Multi-stage build for optimized Strapi deploymentFROM node:18-bullseye-slim AS builderWORKDIR /app# Copy package filesCOPY package*.json ./# Install dependenciesRUN npm ci# Copy source codeCOPY . .# Build Strapi applicationRUN npm run build# Production stageFROM node:18-bullseye-slimWORKDIR /app# Copy package filesCOPY package*.json ./# Install production dependencies onlyRUN npm ci --production# Copy built application from builderCOPY --from=builder /app/dist ./distCOPY --from=builder /app/public ./public# Set environment variablesENV NODE_ENV=productionENV PORT=1337# Expose portEXPOSE 1337# Health checkHEALTHCHECK --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 applicationCMD ["npm", "run", "start"]
-
Create a `.dockerignore` file to exclude unnecessary files from the Docker build:
node_modulesnpm-debug.logyarn-error.log.git.gitignoreREADME.md.env.env.local.env.*.local.vscode.idea.DS_Storedistbuild
- Push your code (with Dockerfile and .dockerignore) to GitHub.
-
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.
- 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
- 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)
-
Update your Strapi configuration to use the mounted volume:
config/plugins.js module.exports = {upload: {config: {provider: 'local',providerOptions: {sizeLimit: 10000000 // 10MB per file}}}}; - 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)
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 configurationPORT=1337NODE_ENV=production
# Database configurationDATABASE_CLIENT=postgresDATABASE_HOST=your-db-host.comDATABASE_PORT=5432DATABASE_NAME=strapi_prodDATABASE_USERNAME=strapi_userDATABASE_PASSWORD=your_secure_passwordDATABASE_SSL=true
# Authentication keys (generate with openssl rand -hex 32)APP_KEYS=key1_value,key2_valueAPI_TOKEN_SALT=your_token_saltADMIN_JWT_SECRET=your_admin_jwt_secretJWT_SECRET=your_jwt_secret
# Application URL (for media serving)PUBLIC_URL=https://example-app.klutch.shOptional Environment Variables
# Email configurationSMTP_HOST=smtp.sendgrid.netSMTP_PORT=587SMTP_USERNAME=apikeySMTP_PASSWORD=your_sendgrid_api_keySMTP_FROM=noreply@example.com
# Cloud storage (S3)AWS_ACCESS_KEY_ID=your_access_keyAWS_SECRET_ACCESS_KEY=your_secret_keyAWS_REGION=us-east-1AWS_BUCKET_NAME=your-strapi-bucket
# LoggingLOG_LEVEL=infoGenerating Secure Keys
# Generate APP_KEYS (comma-separated)openssl rand -hex 32 | tr -d '\n' && echo -n "," && openssl rand -hex 32
# Generate API_TOKEN_SALTopenssl rand -hex 32
# Generate ADMIN_JWT_SECRETopenssl rand -hex 32
# Generate JWT_SECRETopenssl rand -hex 32Content Types & API Setup
Creating a Content Type via Admin Panel
- Log in to the admin panel at
example-app.klutch.sh/admin - Navigate to Content-Type Builder
- Click Create new content-type
- Choose Collection Type (for multiple entries like blog posts) or Single Type (for unique pages like About)
- Define fields (text, rich text, media, relations, etc.)
- Save and your REST/GraphQL API endpoints are generated automatically
Accessing Your APIs
Once content types are created, they’re automatically exposed via:
# REST APIGET /api/postsPOST /api/postsGET /api/posts/:idPUT /api/posts/:idDELETE /api/posts/:id
# GraphQL APIPOST /graphqlTroubleshooting
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_URLor 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+Ron Mac,Ctrl+Shift+Ron Windows) - Check browser console for JavaScript errors
- Verify ADMIN_JWT_SECRET is set correctly
- Check application logs for errors
- Ensure
PUBLIC_URLenvironment 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
- Strapi Official Website
- Strapi Documentation
- Content Types Guide
- Strapi Plugins
- Media Management
- Nixpacks Documentation
- Klutch.sh Dashboard
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.