PORT environment variable. This guide builds a small chat, deploys it with Ingot, then runs it on more than one instance with the Redis adapter and a Sevalla Redis or Valkey database.
At a glance
Environment variables
The internal connection also adds
REDIS_URL, which the server doesn’t read: for a Valkey database it starts with valkey://, which the redis client rejects.
Before you begin
- A Sevalla company with a payment method, and a GitHub, GitLab or Bitbucket account for the repository. See Git providers. More than one instance needs the payment method.
- Node.js 24 or later on your computer.
Create the project
Create a folder and install Socket.IO:/socket.io/, including the browser client at /socket.io/socket.io.js:
server.js
- Each message comes back with
os.hostname(), the name of the instance that relayed it, so you can see which instance a browser talks to. - The server listens on
PORT, which Sevalla sets to the web process’s port (8080by default), on all network interfaces. On your computer, it uses port 3000. - When Sevalla stops an instance, during a deployment for example, it sends
SIGTERM.io.close()disconnects the clients, which reconnect to a running instance on their own, and closes the HTTP server. See How processes run.
index.html
.gitignore
Prepare the project for Sevalla
Set the module type, the start script and the Node.js version inpackage.json:
The project needs no build step and no other configuration: WebSockets need no setting on Sevalla. Run
npm start, open http://localhost:3000 in two browser tabs and send a message: it appears in both. 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.1
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 a Data center. Keep the Size at H1 or choose a larger one, then click Create application. The application opens on its Overview tab with the status Not deployed yet: creating an application doesn’t deploy it.
2
Deploy
Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
3
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.
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 frompackage-lock.json, and the start command from thestartscript.Build log - Wait for the deployment’s status to change to Deployed. The application’s status next to its name changes to Running.
-
Click the domain under the application’s name, for example
chat-x7k2m.sevalla.app, and open it in a second tab too. Each page showsTransport: websocket. Send a message: both tabs show it, followed by the instance’s name, such as(from chat-x7k2m-web-6647f5cff4-blnxv). -
On the Logs tab, the web process’s runtime logs show each connection start on long-polling and move to a WebSocket, such as
l1ivq9ZJh4I-25sEAAAA connected over polling, thenl1ivq9ZJh4I-25sEAAAA upgraded to websocket. See Logs. -
From a terminal, request Socket.IO’s handshake:
The response has a new
sideach time:0{"sid":"hDr0l8vCSRvsvTAEAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000,"maxPayload":1000000}. With the API, Get deployment returns the deployment’s status and Get application deployment logs its build log; see Deploy with the API.


How WebSocket connections behave on Sevalla
Sevalla’s proxy passes WebSocket upgrades through to the web process with no setting to turn on. The CDN and edge caching never cache WebSocket upgrades, and Socket.IO sends its long-polling responses withCache-Control: no-store, so turning caching on doesn’t affect the chat.
- Idle connections. The proxies between the browser and the web process close a WebSocket that carries no data for too long, and the client sees close code
1006. Socket.IO’s server sends a ping every 25 seconds (itspingIntervaldefault) and the client answers, so a Socket.IO connection never sits idle: a client that sends nothing stays connected. With plain WebSockets, such as thewspackage, send a ping or a message at least every 30 seconds, as Socket.IO does. - One instance per connection. A WebSocket stays on the instance it opened on until it closes. When the instance stops, during a deployment or a restart, its clients reconnect and land on a running instance.
- Memory is per instance. Rooms, the list of connected sockets and anything else Socket.IO keeps in memory belong to one instance and are gone when it stops.
Run more than one instance
Sevalla spreads the requests to a web process across its instances without session affinity (sticky sessions): two requests from the same browser can reach different instances. With more than one instance, that breaks two things in Socket.IO:Connect over WebSocket only
Inindex.html, replace const socket = io(); with:
index.html
Share events between instances
1
Create the database
In the sidebar, click Databases, then click Create database. Under Database, choose Valkey or Redis and the newest version. Enter
chat-valkey as the Display name, choose the application’s Data center and a Size, then click Create database. See Create a database.2
Connect the database
On the application’s Networking tab, under Connected services, click Add internal connection. Choose
chat-valkey as the Service, turn on Add environment variables to the application, and click Add internal connection. Sevalla adds REDIS_HOST, REDIS_PORT, REDIS_PASSWORD and REDIS_URL to the application. See Connect an application.3
Install the adapter
Install the Redis adapter and the
redis client:4
Use the adapter in the server
Replace The server connects to the database only when
server.js:server.js
REDIS_HOST is set, so npm start still works on your computer without one. It reads the host, port and password, not REDIS_URL: the redis client accepts only redis:// and rediss:// URLs, and a Valkey database’s REDIS_URL starts with valkey://. The same code works with a Redis database. The error listeners log a lost connection while the client reconnects, instead of stopping the server.Scale the web process
Commit and push the changes, then run two instances:1
Set the instance count
On the application’s Processes tab, click ⋯ on the web process, then click Update process. In Scaling, leave Horizontal auto-scaling off, drag Instance count to
2, then click Update process. See Set the number of instances.2
Deploy
Click the Overview tab, then click Deploy now under Recent deployments. Check the Branch and click Deploy.
Transport: websocket from the start. Send a message from each tab: every tab shows every message, and the instance names after them show that the tabs are connected to both instances. The runtime logs show connected over websocket, with no upgrade.
To check from a terminal, connect 6 clients in a separate folder and send a message from each:
chat-check.mjs
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
.dockerignore
Dockerfile, so you can keep it in a repository that builds with Ingot, but it adds the .dockerignore patterns to its own: its build plan then shows Ignored 6 patterns from .dockerignore, and the Ingot build still passes because none of these patterns removes a file it needs. 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.
📦 Building and pushing docker image using Dockerfile... instead of Ingot’s build plan, and its Dockerfile tab shows your file. The instance count and the database connection stay as they are. When the deployment’s status is Deployed, the chat works as before, over WebSockets on both instances. See Dockerfile.
Deploy changes
With Auto-deploy on, every push to the branch starts a deployment; otherwise, click Deploy now for each change. Each deployment starts new instances, sends them traffic, then stops the old ones: their clients disconnect and reconnect to a new instance. Keep anything that must outlive a connection, such as chat history, in a database, not in the server’s memory. See Deployments.Use environment variables
Add configuration on the application’s Environment variables tab and read it withprocess.env, as the server reads PORT and the REDIS_* variables. Variables apply at the next deployment. See Environment variables.
Add a custom domain
To serve the chat from your own domain, such aschat.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, so scale the web process first. See Custom domains.
Troubleshoot
Deploy with the API
This sequence deploys the finished project from Run more than one instance, with the Redis adapter, a Valkey database and two 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/chat: 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.
1
Look up the location and the sizes
List clusters (CLI:
listClustersV3) returns every location your company can use, with its region ID. This guide uses Frankfurt, Germany, eu-frankfurt-1. List process resource types (listProcessResourceTypesV3) and List database resource types (listDatabaseResourceTypesV3) return the sizes.sevalla resources clusters, sevalla resources process-types and sevalla resources db-types. MCP: get_platform_options.2
Create the application
Create application (CLI:
createApplicationV3) creates the application with a web process and doesn’t deploy it.sevalla apps create --name chat --cluster "$CLUSTER_ID" --source privateGit --git-type github --repository https://github.com/acme/chat --branch main. MCP: create_application with deploy set to false.3
Size and scale the web process
The API creates the web process at S1, while the dashboard creates it at H1, which is enough for the chat. List processes (CLI:
listProcessesV3) returns the web process, and Update process (updateProcessV3) sets its size and 2 instances. To add a custom domain later, leave resource_type_id out and keep S1.sevalla apps processes update "$WEB_ID" --app-id "$APP_ID" --resource-type "$H1_ID" --instances 2. MCP: scale_process with size_id, instances set to 2 and apply_now set to false.4
Create the database
Create database (CLI:
createDatabaseV3) starts creating a Valkey 9.0 database with a password you generate. List database versions (listDatabaseVersionsV3) lists the versions of a location; a Redis database works the same way with "type": "redis".sevalla databases create --name chat-valkey --type valkey --db-version 9.0 --cluster "$CLUSTER_ID" --resource-type "$DB_SIZE_ID" --db-name chat --db-password "$DB_PASSWORD". MCP: create_database, which generates a password and doesn’t return it; set one you know afterwards with sevalla_api_update, resetDatabasePasswordV3 and {"new_password": "..."}.5
Connect the database
Create internal connection (CLI:
createDatabaseInternalConnectionV3) connects the database to the application over the private network. Unlike the dashboard, it adds no environment variables: a later step adds them.sevalla databases connections internal-create "$DB_ID" --target-id "$APP_ID" --target-type app. MCP: sevalla_api_create with createDatabaseInternalConnectionV3.6
Read the database details
Get database (CLI:
getDatabaseV3) returns the database’s internal hostname and port. The loop waits until its status is ready.sevalla databases get "$DB_ID" --json. MCP: get_database.7
Add the environment variables
Create environment variable (CLI:
createEnvironmentVariableV3) adds one variable per call. add_var sends each one as available during runtime only. These are the variables the dashboard’s internal connection adds, except REDIS_URL, which the code doesn’t read:sevalla apps env-vars create "$APP_ID" --key REDIS_HOST --value "$(jq -r .internal_hostname <<< "$DB")", once per variable; the CLI makes each one available during the build too. MCP: set_environment_variables with the three variables in one call, each with runtime true and buildtime false.8
Turn on auto-deploy
Update application (CLI:
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.9
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.10
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.11
Check the deployment
Get application deployment logs (Then run the checks in Verify the deployment and Scale the web process with
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:$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.