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
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: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:
npm run develop on your computer.
Prepare the project for Sevalla
Store uploads in object storage
Strapi reads the files inconfig/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:
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:
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:
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.Create the database
orders-db as the Display name, choose a Data center and a Size, then click Create database. See Create a database for every field.Create the bucket
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.Create the application
Connect the database
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.Add the environment variables
node command printed into the first Key field: the sheet fills in one variable per line. Then add these and click Add environment variables:Deploy
Turn on auto-deploy
Verify the deployment
-
On the Deployments tab, click the deployment. Its Build log shows Ingot’s build plan: Node.js 24 from
engines.node, npm fromengines.npminpackage.json, whichcreate-strapi-appsets to>=6.0.0, the start command from thestartscript, and dev dependencies removed after the build.Later, the build log showsBuild logstrapi buildbuilding the admin panel:✔ Building admin panel. - Wait for the deployment’s status to change to Deployed. The application’s status next to its name changes to Running.
-
Open the application’s domain followed by
/admin, for examplehttps://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. -
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":{}}}: theproductsroute 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.


Add content and read it from the API
-
In the admin panel, click Content Manager, click Product, then click Create new entry. Enter
Coffee mugas 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. -
Click Settings, then API Tokens, then Create new API Token. Enter
storefront-readas 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. -
Read the products, with the token instead of
<token>and your application’s domain:The response lists the product, and itsimage.urlis on the bucket’s public domain, such ashttps://storefront-media-q8w2e.sevalla.storage/mug_dbad2b89ae.png. Open it: the image loads. - 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.
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:
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 with503. Strapi answers /_health with 204 once it’s started; make that the web process’s readiness probe:
Open the web process
Turn on the readiness probe
/_health. Click Update process, then deploy. The probe applies with the deployment.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 aDockerfile and a .dockerignore to the root of the repository:
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:
Choose the Dockerfile build strategy
Dockerfile and the Docker context ., then click Update build strategy.Deploy
📦 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 withnpm 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 inconfig/ 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 ascms.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 thecurl 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_KEYenvironment variable. The CLI reads the same key fromSEVALLA_API_TOKEN. curl,jqandopenssl, 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
400andCannot access repository https://github.com/acme/storefront: Repository not found or you don't have access.
status and message instead of the resource, and jq -r .id prints null: stop and read the message.
Look up the location and the database size
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.sevalla resources clusters and sevalla resources db-types. MCP: get_platform_options.Create the application
createApplicationV3) creates the application with a web process of size S1, the size Strapi needs, and doesn’t deploy it.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.Create the database
createDatabaseV3) starts creating a PostgreSQL 18 database with a password you generate. List database versions (listDatabaseVersionsV3) lists the versions of a location.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://.Connect the database
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.sevalla databases connections internal-create "$DB_ID" --target-id "$APP_ID" --target-type app. MCP: sevalla_api_create with createDatabaseInternalConnectionV3.Create the bucket
createObjectStorageV3) creates the bucket in Western Europe (weur), close to Frankfurt, with public access on.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.Read the database and bucket details
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.sevalla databases get "$DB_ID" --json and sevalla object-storage get "$BUCKET_ID" --json. MCP: get_database, and sevalla_api_read with getObjectStorageV3.Add the environment variables
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: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.Turn on the readiness probe
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.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.Turn on auto-deploy
updateApplicationV3) with auto_deploy deploys every push to the branch.sevalla apps update "$APP_ID" --data '{"auto_deploy": true}'. MCP: sevalla_api_update with updateApplicationV3.Deploy
triggerDeploymentV3) builds the latest commit of the branch and rolls it out.sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.Wait for the 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.sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.Check the deployment
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: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.