Tutorial

From app to HTTPS URL
in under 10 minutes.

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.

Node.js installed
Docker installed & running
clusterkit CLI installed
1
Install CLI
2
Write app
3
Dockerfile
4
Push image
5
Install control plane
6
Init & configure world
7
Generate & up
Live 🎉
1

Install the ClusterKit CLI

One command installs clusterkit globally from our npm registry.

bash
$ curl -fsSL https://cli.clustgo.com/install.sh | sh
What this does: Downloads and runs a small script that installs clusterkit-cli from npm.sillygooseia.com using npm. Node.js 18+ and npm must already be on your PATH.
bashverify
$ clusterkit --version

2

Write your web app

A minimal Express server — swap this for your real app at any time.

bash
$ mkdir my-app && cd my-app
$ npm init -y
$ npm install express
javascriptindex.js
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}`));
Tip: Any framework works — Next.js, FastAPI, Rails, Go. ClustGo only cares about the container image, not what’s inside it.

3

Write a Dockerfile

A production-ready, minimal image using the Alpine Node base.

dockerfileDockerfile
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"]
text.dockerignore
node_modules
.git
*.md

Build and smoke-test locally before pushing:

bash
$ 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

4

Push to a container registry

ClusterKit pulls your image by URL. Use any registry you already have.

bashGitHub Container Registry
# 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
Also works with: Docker Hub (docker.io/you/my-app), a private VPS registry (registry.example.com:5000/my-app), or any OCI-compliant registry.

5

Install the ClusterKit control plane

The control plane runs Traefik (ingress + TLS) and the ClusterKit control server. It needs to be running before any world can be deployed.

bash
# 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")
Note: 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.


6

Initialize and configure your world

A world is an isolated runtime environment scoped to your app.

bash
# 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:

jsonclusterkit.json
{
  "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:

bash
$ clusterkit enable postgres
postgres enabled.
User:     app
Password: Xk9mQ2rLpT
Host:     postgres
# Save these now — they won't be shown again.
Important: ClusterKit does not store generated credentials. Copy them to a secrets manager or password manager before moving on.

7

Generate the Compose file, then bring the world up

clusterkit generate compiles your manifest into a Docker Compose file. clusterkit up registers the world with the control plane and starts it.

bash
# 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:

my-app-world/ ├── clusterkit.json # your manifest — edit this └── out/ └── compose.yml # generated — do not edit by hand
Re-run 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:

bash
$ 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
Your app is live at https://my-app.yourdomain.com with a valid TLS certificate. The ClusterKit control UI at http://localhost:3001 shows the world status and logs.

Day-2 operations

Common tasks once your world is running.

bash
# 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

What’s next?