At a glance
What this guide adds to the Express application:Environment variables
The Express application’s own variables are in its table.
Before you begin
- An Express application deployed on Sevalla, as in Deploy Express.
- Node.js 20 or later in the application, which the AWS SDK for JavaScript (
@aws-sdk/client-s3) needs. The Express guide pins Node.js 24.
Create a bucket
In the sidebar, click Object storage, then click Create object storage. Enterstorefront-media as the Name, choose the Location closest to the application, and click Create object storage. See Create a bucket for every field.
Add the bucket details to the application
1
Copy the values
On the bucket’s Settings tab, copy the Endpoint and Bucket name under Details, and the Access key and Secret key under Access keys.
2
Add them as environment variables
On the application’s Environment variables tab, click Add environment variables. Add these variables, keep Available during runtime selected, and click Add environment variables:
They apply at the next deployment.
Install the AWS SDK
@aws-sdk/client-s3sends the S3 requests.@aws-sdk/lib-storagestreams uploads and splits large files into parts.@aws-sdk/s3-request-presignercreates presigned URLs.
Create the client
Create one client for the whole application:storage.js
auto. The two WHEN_REQUIRED options make the SDK add checksums only to the requests that need them. Without them, it signs the checksum of an empty body (x-amz-checksum-crc32=AAAAAA==) into every presigned upload URL, which doesn’t match the file that’s uploaded with it.
Add routes for files
Add three routes toserver.js: one that streams an upload into the bucket, one that lists the files, and one that redirects to a presigned URL for a file:
server.js
export S3_ENDPOINT=...), run npm start, and upload a file with the curl command in Deploy and verify, using http://localhost:3000.
Deploy and verify
Commit and push the changes,package-lock.json included: Ingot installs the new packages from it with npm ci and starts the server from the start script as before. The build doesn’t need the bucket’s variables, only the running server does. With auto-deploy on, the push starts a deployment; otherwise, start one with Deploy now on the application’s Overview tab. When the deployment’s status changes to Deployed:
-
Upload a PNG file from your computer, such as
logo.pngin the current folder. Use your application’s domain instead ofstorefront-x7k2m.sevalla.app:The response is{"key":"uploads/logo.png"}. -
Open
https://storefront-x7k2m.sevalla.app/files/logo.pngin a browser. The application redirects to a presigned URL on the bucket’s endpoint, and the image opens. -
On the bucket’s Objects tab, open the
uploadsfolder.logo.pngis there.
500, read the application’s runtime logs for the S3 error:
For other errors, see the connection troubleshooting table.
Let browsers upload directly
Uploads that pass through the application use its bandwidth and memory. To let the browser send a file straight to the bucket, create a presigned URL forPutObjectCommand on the server and give it to the browser, and add a CORS rule to the bucket for your site’s origin. See Share files with presigned URLs.
Serve files publicly
For files that anyone may read, such as product images, turn on public access on the bucket’s Settings tab and link to the object’s public URL instead of signing one each time. Cloudflare’s CDN caches public files. See Public URLs.Deploy with the API
This sequence adds the bucket of this guide to the application from Deploy Express through the Sevalla API, so an agent or a script can run it as is.APP_ID is that application, and the changes on this page are pushed to its branch. 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 and jq. A call that fails returns status and message instead of the resource, and jq -r .id prints null: stop and read the message.
1
Create the bucket
Create object storage bucket (CLI:
createObjectStorageV3) creates a private bucket. location is where most reads and writes come from: weur, Western Europe, suits an application in Frankfurt, Germany.sevalla object-storage create --name storefront-media --location weur. MCP: sevalla_api_create with createObjectStorageV3 and the same body.2
Read the bucket details
Get object storage bucket (CLI:
getObjectStorageV3) returns the bucket’s endpoint, bucket_name, access_key and secret_key.sevalla object-storage get "$BUCKET_ID" --json. MCP: sevalla_api_read with getObjectStorageV3.3
Add the environment variables
Create environment variable (CLI:
createEnvironmentVariableV3) adds one variable per call. add_var sends each one as available during runtime only, since the build doesn’t need them:sevalla apps env-vars create "$APP_ID" --key S3_BUCKET --value "$(jq -r .bucket_name <<< "$BUCKET")", once per variable; the CLI makes each one available during the build too. MCP: set_environment_variables with the four variables in one call, each with runtime true and buildtime false.4
Deploy
Trigger deployment (CLI:
triggerDeploymentV3) builds the latest commit of the branch and rolls it out.sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.5
Wait for the deployment
Get deployment (CLI:
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.sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.6
Check the deployment
List application domains (The output, with your file’s size in bytes:List objects (
listApplicationDomainsV3) returns the application’s domain. Upload logo.png from the current folder, list the files, and follow the redirect to the presigned URL:listObjectStorageObjectsV3) lists the same file from the Sevalla side. CLI: sevalla apps domains list --app-id "$APP_ID" and sevalla object-storage objects list --bucket-id "$BUCKET_ID". MCP: get_application, and sevalla_api_read with listObjectStorageObjectsV3.