Skip to content

Deploy a Node.js API

This guide deploys a minimal Node.js API from source. It focuses on the Node-specific parts of a reliable container: binding to 0.0.0.0, using the platform-provided PORT, running as a non-root user, and exposing a health endpoint.

For a framework such as Express, Fastify, or NestJS, keep the same container and networking rules and replace server.js with your application entry point.

  • Install 1ctl
  • Authenticate with 1ctl auth login
  • Install jq so you can read the generated URL from JSON output

Confirm your active account and organization:

Terminal window
1ctl auth status

Run these commands in a terminal:

Terminal window
mkdir satusky-node-api
cd satusky-node-api
cat > package.json <<'JSON'
{
"name": "satusky-node-api",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node server.js"
}
}
JSON
cat > server.js <<'JS'
const http = require('node:http');
const port = Number(process.env.PORT || 3000);
const server = http.createServer((request, response) => {
response.setHeader('content-type', 'application/json');
if (request.url === '/health') {
response.end(JSON.stringify({ status: 'ok', runtime: process.version }));
return;
}
response.statusCode = 404;
response.end(JSON.stringify({ error: 'not found' }));
});
server.listen(port, '0.0.0.0', () => {
console.log(`listening on 0.0.0.0:${port}`);
});
JS

Binding to 0.0.0.0 is required. Binding to localhost makes the process unreachable from the platform route and health checks.

Create Dockerfile:

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --chown=node:node package.json server.js ./
EXPOSE 3000
USER node
CMD ["npm", "start"]

USER node avoids running the application as root. Keep --chown=node:node on copied application files; otherwise a restricted runtime user may not be able to read them.

Exclude local and sensitive files from the cloud build context:

Terminal window
cat > .dockerignore <<'IGNORE'
node_modules
.git
.env
IGNORE

Do not put credentials in the image or build context. Use SatuSky secrets when your real application needs them.

Create satusky.toml:

[app]
name = "satusky-node-api"
port = 3000
[build]
dockerfile = "Dockerfile"

The configured port must match the port exposed by the process. The organization and namespace come from your active 1ctl profile, so they do not belong in this file.

From the project directory, run:

Terminal window
1ctl deploy --config satusky.toml --health-path /health

SatuSky uploads the build context, builds the Dockerfile in the cloud, pushes a private image for both linux/amd64 and linux/arm64, and submits the deployment. You do not need a local Docker daemon.

The command returns an operation ID and deployment ID after the deployment is accepted. Check reconciliation separately:

Terminal window
1ctl app status satusky-node-api

Run the status command again while the workload or public route is still progressing. Before treating the generated hostname as public-ready, wait for its DNS condition to be verified; a hostname reservation, pending, nxdomain, wrong_target, or error is not a successful DNS result.

Read the generated HTTPS URL from the deployment record:

Terminal window
APP_URL=$(1ctl -o json app get satusky-node-api | jq -r '.domain')
echo "$APP_URL"

Call the health endpoint:

Terminal window
curl --fail --show-error --silent "$APP_URL/health"

A healthy response looks like:

{ "status": "ok", "runtime": "v22.23.1" }

If the request is not ready yet, inspect status and logs before changing the application:

Terminal window
1ctl app status satusky-node-api
1ctl logs --app satusky-node-api --tail 50

The startup log should include:

listening on 0.0.0.0:3000

Change the JSON response in server.js, then run the same deployment command:

Terminal window
1ctl deploy --config satusky.toml --health-path /health

SatuSky builds a new image and updates the existing application because the [app].name is unchanged. Deployment strategies and recovery are covered in the rollout and rollback guide rather than duplicated here.

Delete the test application and its runtime resources:

Terminal window
1ctl app delete satusky-node-api --yes
  • Source was built in the cloud without a local Docker daemon.
  • The image supports both AMD64 and ARM64 machines.
  • Node runs as a non-root user and listens on the correct network interface.
  • The configured application port matches the Node process port.
  • /health provides a simple readiness and smoke-test endpoint.