Skip to main content
A Strapi project runs on Sevalla as an application: Ingot installs its dependencies, builds the admin panel with the build script, strapi build, and starts the web process with the start script, strapi start, which listens on the port in the PORT environment variable. Its content lives in a Sevalla PostgreSQL database, and the files editors upload go to an object storage bucket, so they survive every deployment.

At a glance

Environment variables

The internal connection also adds DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME and DB_PASSWORD, which Strapi doesn’t read.

Before you begin

  • A Sevalla company with a payment method, and a GitHub, GitLab or Bitbucket account for the repository. See Git providers.
  • Node.js 24 on your computer. Strapi 5 supports Node.js 20 to 26.
  • PostgreSQL on your computer for development. With Docker:

Create the project

Create a Strapi project in TypeScript that uses the local PostgreSQL database, and install the S3 upload provider:
The flags answer every question create-strapi-app would ask: no Strapi Cloud account, no example data, npm, and the database on your computer. The command writes the database settings and random secrets to .env, which .gitignore keeps out of the repository. Add a content type for products, with a name and an image. Strapi doesn’t let you create content types in production, so they live in the repository:
src/api/product/content-types/product/schema.json
src/api/product/controllers/product.ts
src/api/product/routes/product.ts
src/api/product/services/product.ts
The Content-Type Builder in the admin panel writes the same files when you create a type with npm run develop on your computer.

Prepare the project for Sevalla

Store uploads in object storage

Strapi reads the files in config/env/production/ on top of config/ when NODE_ENV is production, which it is on Sevalla. Add the upload provider there, so uploads go to the bucket on Sevalla and stay in public/uploads on your computer:
config/env/production/plugins.ts
Sevalla buckets run on Cloudflare R2, which ignores the region, but the AWS SDK needs one: auto. baseUrl makes Strapi save each file’s address on the bucket’s public domain. The upload limits in config/plugins.ts, such as the file types Strapi refuses, still apply. Then let the admin panel show images from the bucket. Strapi’s default Content Security Policy only allows its own address:
config/env/production/middlewares.ts

Pin the Node.js version

create-strapi-app sets engines.node to >=20.0.0 <=26.x.x, a range in which Ingot builds on its default line. Pin Node.js 24, so the build doesn’t change when a new Ingot release moves its default line:
Strapi needs no other configuration: the build needs no environment variables, and Ingot removes the dev dependencies after the build. Run npm run develop and open http://localhost:1337/admin to check the project locally. Then commit everything and push it to your Git provider:

Deploy to Sevalla

These steps build with Ingot. To build from your own Dockerfile instead, see Deploy with a Dockerfile. Generate the production secrets first. Strapi signs sessions and tokens with them, so keep them in a password manager: changing them later signs every editor out and invalidates API tokens.
1

Create the database

In the sidebar, click Databases, then click Create database. Under Database, choose PostgreSQL and the newest version. Enter orders-db as the Display name, choose a Data center and a Size, then click Create database. See Create a database for every field.
2

Create the bucket

In the sidebar, click Object storage, then click Create object storage. Enter storefront-media as the Name, choose the Location closest to the database, and click Create object storage. On the bucket’s Settings tab, under Public access, click Enable public access and confirm. See Create a bucket.
3

Create the application

In the sidebar, click Applications, then click Create > Application. Under Source, click Private repository, then choose the Git provider, the Repository and the Branch. Under Location, choose the same Data center as the database. Choose S1 or a larger Size: Strapi needs more than the 300 MB of memory that H1 has, and an H1 instance is stopped as soon as Strapi starts. Click Create application. The application opens on its Overview tab with the status Not deployed yet: creating an application doesn’t deploy it.
4

Connect the database

Click the Networking tab. Under Connected services, click Add internal connection. Choose orders-db as the Service and turn on Add environment variables to the application. In the list of variables, change the key DB_URL to DATABASE_URL, then click Add internal connection. See Connect an application.
5

Add the environment variables

Click the Environment variables tab, then click Add environment variables. Paste the six lines the node command printed into the first Key field: the sheet fills in one variable per line. Then add these and click Add environment variables:See Connect with the S3 API for where each bucket value is.
6

Deploy

Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
7

Turn on auto-deploy

To deploy every push to the branch automatically, click the Settings tab, then click Update auto-deploy in the Auto-deploy section. Turn on Auto-deploy and click Update auto-deploy. See Deployments.
Strapi creates and updates its tables when it starts, from the content types in the repository, so the project needs no migration job.

Verify the deployment

  1. On the Deployments tab, click the deployment. Its Build log shows Ingot’s build plan: Node.js 24 from engines.node, npm from engines.npm in package.json, which create-strapi-app sets to >=6.0.0, the start command from the start script, and dev dependencies removed after the build.
    Build log
    Later, the build log shows strapi build building the admin panel: ✔ Building admin panel.
  2. Wait for the deployment’s status to change to Deployed. The application’s status next to its name changes to Running.
  3. Open the application’s domain followed by /admin, for example https://storefront-x7k2m.sevalla.app/admin. The page shows Welcome to Strapi!. Fill in First name, Last name, Email and Password, confirm the password, and click Let’s start to create the first administrator.
  4. From a terminal, check the health endpoint and the API, with your application’s domain:
    The first command prints 204. The second prints {"data":null,"error":{"status":403,"name":"ForbiddenError","message":"Forbidden","details":{}}}: the products route exists, and reading it needs an API token. With the API, Get deployment returns the deployment’s status and Get application deployment logs its build log; see Deploy with the API.
The application header with its name, Running status, repository, branch and the domain that opens the applicationThe application header with its name, Running status, repository, branch and the domain that opens the application
If the deployment fails, read its build log, then see Troubleshoot Ingot builds. If it deploys but the domain doesn’t respond, read the application’s runtime logs and see Troubleshoot.

Add content and read it from the API

  1. In the admin panel, click Content Manager, click Product, then click Create new entry. Enter Coffee mug as the name, add an image from your computer in the image field, and click Save. Strapi uploads the image to the bucket: it’s on the bucket’s Objects tab.
  2. Click Settings, then API Tokens, then Create new API Token. Enter storefront-read as the Name, choose a Token duration, such as Unlimited, and Read-only as the Token type, then click Save. Copy the token: Strapi shows it only once.
  3. Read the products, with the token instead of <token> and your application’s domain:
    The response lists the product, and its image.url is on the bucket’s public domain, such as https://storefront-media-q8w2e.sevalla.storage/mug_dbad2b89ae.png. Open it: the image loads.
  4. Deploy again with Deploy now, then repeat the request. The product and its image are still there: the content is in PostgreSQL and the file in the bucket, not on the instance’s disk, which each deployment replaces.
To do the same from a terminal, for example as an agent, use Strapi’s admin API. register-admin works only while the project has no administrator, so run this instead of step 3 of Verify the deployment, with mug.png in the current folder:
The password has an uppercase letter, a lowercase letter and a digit, as Strapi requires; keep it to sign in as ana@example.com. The output is the new product, then the product as the read-only token reads it, with its image on the bucket’s public domain; Strapi adds a different suffix to the file name on each upload:

Send traffic only to started instances

Strapi takes a few seconds to start. Without a readiness probe, Sevalla sends requests to a new instance as soon as its container starts, so for a moment during each deployment requests fail with 503. Strapi answers /_health with 204 once it’s started; make that the web process’s readiness probe:
1

Open the web process

On the Processes tab, open the web process’s menu and click Update process.
2

Turn on the readiness probe

Under Health checks, turn on Readiness probe with the Path /_health. Click Update process, then deploy. The probe applies with the deployment.
From then on, each deployment moves traffic to the new instances only after Strapi answers. See Health checks.

Deploy with a Dockerfile

Ingot needs no Dockerfile. Write your own when you want full control of the image: the base image, the system packages and every layer. Add a Dockerfile and a .dockerignore to the root of the repository:
Dockerfile
.dockerignore
Commit and push both files. Ingot doesn’t use the Dockerfile, so you can keep it in a repository that builds with Ingot, but it adds the .dockerignore patterns to its own, and its build plan lists them in an Ignored row. Then switch the application to the Dockerfile build strategy:
1

Choose the Dockerfile build strategy

On the application’s Settings tab, in the Build strategy section, click Update build strategy. Click Dockerfile, keep the Dockerfile path Dockerfile and the Docker context ., then click Update build strategy.
2

Deploy

Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
The deployment’s Build log shows 📦 Building and pushing docker image using Dockerfile... instead of Ingot’s build plan, and its Dockerfile tab shows your Dockerfile. When the deployment is Deployed, the request in Add content and read it from the API returns the same product and image. See Dockerfile for build arguments and private base images.

Deploy changes

With Auto-deploy on, every push to the branch starts a deployment; otherwise, click Deploy now for each change. Each deployment builds the image, then replaces the web process’s instances; Strapi updates the database tables for changed content types when the new instances start. Change content types with npm run develop on your computer and commit the files it writes. See Deployments.

Use environment variables

Add configuration on the application’s Environment variables tab and read it in config/ files with env('NAME'). Variables apply at the next deployment. Variables that start with STRAPI_ADMIN_ are built into the admin panel, so they must be Available during buildtime, and a change needs a new build. See Environment variables.

Add a custom domain

To serve the application from your own domain, such as cms.example.com, add the domain on the application’s Domains tab and create the DNS records that Sevalla shows. Custom domains need a web process larger than H1, which S1 is. See Custom domains.

Troubleshoot

Deploy with the API

This sequence creates the same deployment as Deploy to Sevalla and Send traffic only to started instances through the Sevalla API, so an agent or a script can run it as is. Each step names the operation, shows the curl command, and gives the matching CLI command and MCP server tool. You need:
  • An API key with the Company developer role, in the SEVALLA_API_KEY environment variable. The CLI reads the same key from SEVALLA_API_TOKEN.
  • curl, jq and openssl, and the project pushed to GitHub as in Prepare the project for Sevalla.
  • A GitHub account that can read the repository, connected to Sevalla. See Git providers. The API can’t list connected accounts: when none can read the repository, Create application fails with 400 and Cannot access repository https://github.com/acme/storefront: Repository not found or you don't have access.
A call that fails returns status and message instead of the resource, and jq -r .id prints null: stop and read the message.
1

Look up the location and the database size

List clusters (listClustersV3) returns every location your company can use, with its region ID. This guide uses Frankfurt, Germany, eu-frankfurt-1. List database resource types (listDatabaseResourceTypesV3) returns the database sizes; DB1 is the smallest.
CLI: sevalla resources clusters and sevalla resources db-types. MCP: get_platform_options.
2

Create the application

Create application (createApplicationV3) creates the application with a web process of size S1, the size Strapi needs, and doesn’t deploy it.
CLI: sevalla apps create --name storefront --cluster "$CLUSTER_ID" --source privateGit --git-type github --repository https://github.com/acme/storefront --branch main. MCP: create_application with deploy set to false.
3

Create the database

Create database (createDatabaseV3) starts creating a PostgreSQL 18 database with a password you generate. List database versions (listDatabaseVersionsV3) lists the versions of a location.
CLI: sevalla databases create --name orders-db --type postgresql --db-version 18 --cluster "$CLUSTER_ID" --resource-type "$DB_SIZE_ID" --db-name storefront --db-user storefront --db-password "$DB_PASSWORD". MCP: create_database, which generates the password and doesn’t return it; with connect_to_application_id and env_var_name set to DATABASE_URL, it also does the next step and stores the connection URL as postgresql://..., which Strapi reads like postgres://.
4

Connect the database

Create internal connection (createDatabaseInternalConnectionV3) connects the database to the application over the private network. Unlike the dashboard, it adds no environment variables: the next steps add DATABASE_URL.
CLI: sevalla databases connections internal-create "$DB_ID" --target-id "$APP_ID" --target-type app. MCP: sevalla_api_create with createDatabaseInternalConnectionV3.
5

Create the bucket

Create object storage bucket (createObjectStorageV3) creates the bucket in Western Europe (weur), close to Frankfurt, with public access on.
CLI: sevalla object-storage create --name storefront-media --location weur, then sevalla object-storage cdn-domain enable "$BUCKET_ID" for public access. MCP: sevalla_api_create with createObjectStorageV3 and the same body.
6

Read the database and bucket details

Get database (getDatabaseV3) returns the database’s internal hostname and port, once its status is ready. Get object storage bucket (getObjectStorageV3) returns the bucket’s endpoint, name, keys and public domain.
CLI: sevalla databases get "$DB_ID" --json and sevalla object-storage get "$BUCKET_ID" --json. MCP: get_database, and sevalla_api_read with getObjectStorageV3.
7

Add the environment variables

Create environment variable (createEnvironmentVariableV3) adds one variable per call. add_var sends each one as available during runtime only, since the build needs none. DATABASE_URL has the format the dashboard uses, and the six secrets come from openssl:
List environment variables (listEnvironmentVariablesV3) returns the values later; the MCP server’s tools don’t. Don’t change the secrets afterwards: new ones sign every editor out and invalidate API tokens.CLI: sevalla apps env-vars create "$APP_ID" --key DATABASE_CLIENT --value postgres, once per variable; the CLI makes each one available during the build too. MCP: set_environment_variables with all the variables in one call, each with runtime true and buildtime false.
8

Turn on the readiness probe

List processes (listProcessesV3) returns the web process, and Update process (updateProcessV3) gives it the probe the dashboard creates. Send timeoutSeconds: without it, the API sets 120 seconds, not the dashboard’s 1.
CLI: sevalla apps processes list --app-id "$APP_ID"; the CLI can’t set probes. MCP: get_application, then sevalla_api_update with updateProcessV3 and the same body.
9

Turn on auto-deploy

Update application (updateApplicationV3) with auto_deploy deploys every push to the branch.
CLI: sevalla apps update "$APP_ID" --data '{"auto_deploy": true}'. MCP: sevalla_api_update with updateApplicationV3.
10

Deploy

Trigger deployment (triggerDeploymentV3) builds the latest commit of the branch and rolls it out.
CLI: sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.
11

Wait for the deployment

Get deployment (getDeploymentV3) returns the deployment’s status: waiting, then inProgress, then success or failed. The loop polls every 15 seconds for up to 30 minutes and keeps polling through a failed request. A Strapi deployment takes about 4 minutes.
CLI: sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.
12

Check the deployment

Get application deployment logs (getApplicationDeploymentLogsV3) returns the build log, which starts with the build plan in Verify the deployment and, on a failed deployment, ends with the error. List application domains (listApplicationDomainsV3) returns the application’s domain:
The last command prints 204. Then run the checks in Verify the deployment and Add content and read it from the API with $APP_URL as the domain.CLI: sevalla apps deployments logs "$DEPLOYMENT_ID" --app-id "$APP_ID" and sevalla apps domains list --app-id "$APP_ID". MCP: get_deployment_logs and get_application.