We'll take a plain Node.js web app, containerize it, push it to a registry, and deploy it live using ClusterKit — no Kubernetes, no Helm, no nonsense.
One command installs clusterkit globally from our npm registry.
$ curl -fsSL https://cli.clustgo.com/install.sh | sh
clusterkit-cli from npm.sillygooseia.com using npm. Node.js 18+ and npm must already be on your PATH.$ clusterkit --version
A minimal Express server — swap this for your real app at any time.
$ mkdir my-app && cd my-app
$ npm init -y
$ npm install express
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => res.send('<h1>Hello from ClustGo</h1>'));
app.listen(port, () => console.log(`listening on ${port}`));
A production-ready, minimal image using the Alpine Node base.
FROM node:20-alpine
WORKDIR /app
# Cache deps separately from source
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV PORT=3000
EXPOSE 3000
CMD ["node", "index.js"]
node_modules
.git
*.md
Build and smoke-test locally before pushing:
$ docker build -t my-app:latest .
$ docker run --rm -p 3000:3000 my-app:latest
listening on 3000
# verify at http://localhost:3000, then Ctrl+C
ClusterKit pulls your image by URL. Use any registry you already have.
# Authenticate once
$ echo $CR_PAT | docker login ghcr.io -u YOUR_GITHUB_USER --password-stdin
# Tag with your registry path
$ docker tag my-app:latest ghcr.io/YOUR_GITHUB_USER/my-app:latest
# Push
$ docker push ghcr.io/YOUR_GITHUB_USER/my-app:latest
latest: digest: sha256:abc123... size: 12345
docker.io/you/my-app), a private VPS registry (registry.example.com:5000/my-app), or any OCI-compliant registry.The control plane runs Traefik (ingress + TLS) and the ClusterKit control server. It needs to be running before any world can be deployed.
# Start the control plane — Traefik + control-server UI
$ clusterkit install --image silentcoil.sillygooseia.com:5000/sillygooseia/clusterkit-control-server:latest --port 3001
ClusterKit control plane is running at http://localhost:3001
# Point the CLI at the running control plane
$ export CLUSTERKIT_CONTROL_URL=http://localhost:3001
# (Windows PowerShell: $env:CLUSTERKIT_CONTROL_URL = "http://localhost:3001")
clusterkit install only needs to be run once per server. The control plane stays running across world deployments and reboots.Open http://localhost:3001 to confirm the control-server UI is responding before continuing.
A world is an isolated runtime environment scoped to your app.
# Create a dedicated folder for the world definition
$ mkdir my-app-world && cd my-app-world
# Initialize — creates clusterkit.json
$ clusterkit init
Initialized clusterkit world: created clusterkit.json
Now edit clusterkit.json to tell ClusterKit about your image, port, and domain:
{
"name": "my-app",
"image": "ghcr.io/YOUR_GITHUB_USER/my-app:latest",
"port": 3000,
"domain": "my-app.yourdomain.com",
"modules": {
"postgres": { "enabled": false },
"redis": { "enabled": false }
}
}
Need a database? Enable it now — credentials are generated once and printed to your terminal:
$ clusterkit enable postgres
postgres enabled.
User: app
Password: Xk9mQ2rLpT
Host: postgres
# Save these now — they won't be shown again.
clusterkit generate compiles your manifest into a Docker Compose file. clusterkit up registers the world with the control plane and starts it.
# Step A — compile manifest → out/compose.yml
$ clusterkit generate
Generated out/compose.yml
# Step B — register with control plane and start
$ clusterkit up
Registering world 'my-app' with control-server (http://localhost:3001)...
Bringing world 'my-app' up via control-server...
[+] Running 2/2
✔ my-app-app-1 Started
✔ my-app-traefik-1 Started
After generate your world folder looks like this:
generate whenever you change clusterkit.json (new image tag, enable a module, change domain). Then run clusterkit up to apply the changes.Watch everything running:
$ clusterkit status
CONTAINER STATUS PORTS
my-app-app-1 Up 8 seconds 3000/tcp
my-app-traefik-1 Up 8 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
https://my-app.yourdomain.com with a valid TLS certificate. The ClusterKit control UI at http://localhost:3001 shows the world status and logs.
Common tasks once your world is running.
# Tail logs
$ clusterkit logs
# Deploy a new image version
# 1. Update "image" in clusterkit.json
# 2. Re-generate and re-up:
$ clusterkit generate
$ clusterkit up
# Add Redis to a running world
$ clusterkit enable redis
$ clusterkit generate
$ clusterkit up
# Stop the world
$ clusterkit down