Skip to content

Cubeship — Your infrastructure. Ready to ship.

A PaaS you run on your own server. docker push, and it is live — with HTTPS, a database beside it, and a second machine when one stops being enough.

Install · First five minutes · Deploy an app · Databases · Backups · More machines · MCP · Changelog


Contents

What you get

One command on a fresh VPS, and the box is running:

A dashboard at your instance's address, over HTTPS
A registry docker push to it and the app deploys
Zero-downtime deploys the new container has to look healthy before the old one goes
Certificates Let's Encrypt, renewed for you, nothing to configure
Databases Postgres, MySQL, MariaDB, Redis, MongoDB — one click, wired into the app's environment
Object storage a MinIO on the box, or your own S3 bucket
Backups every database dumped on a schedule into a bucket off the machine, and restored from the same screen
Builds from a Dockerfile, or from a repository with no Dockerfile at all
More machines add a second server and apps spread across both
Charts what every container is using, and what the machine underneath is doing
An API, a CLI and MCP everything the dashboard does, scriptable — and an endpoint an agent drives directly
Templates recipes for apps and the databases they need, published by the community at cubeship.dev/templates

Everything Cubeship runs is a container, the daemon included: Postgres, the registry, the proxy, the builder and every app of yours are its siblings, with administrative services on a separate network from apps. Nothing else is installed on the host.

Install

On the server, as root:

curl -fsSL https://cubeship.dev/install.sh | sh

It installs Docker if the box has not got it, pulls two images, starts the daemon, and prints the address to open and the token to claim it with.

It is one file — read it first if you would rather not pipe a script into a shell, which is a reasonable thing to prefer.

It installs an exact release, not a moving tag: it asks which release is newest and pins that number, so running the same command again tomorrow gives you the same thing. To choose:

curl -fsSL .../install.sh | sh -s -- --version 0.1.0

A version with a -rc.1 on it is a release candidate. They are published like any other release and are never what you get by default.

What it needs

  • Linux, x86-64 or arm64. Debian and Ubuntu are what the installer knows how to put Docker on; on anything else, install Docker first and the script will use it.
  • Ports 80 and 443 free, for the proxy and for certificates.
  • Port 3000 free on loopback, for recovery through an SSH tunnel. The installer refuses rather than fighting whatever is already there.
  • Root, because it installs Docker and writes to /var/lib/cubeship.

Installing your own build

To run your own code instead of a published release, put the repository on the server and build there. The build runs inside Docker, so the server needs no Go and no Node:

git clone https://github.com/cubeshipd/cubeship && cd cubeship
sudo ./install.sh --local

First five minutes

Claim the instance

Open the HTTPS address the installer printed and create the first account. It asks for the setup token, which the installer printed and which is also in /var/lib/cubeship/setup-token.

If the domain or certificate is unavailable, run this on your computer:

ssh -L 3000:127.0.0.1:3000 root@<your-server>

Keep the tunnel open and visit http://localhost:3000. The recovery port is bound to the server's loopback interface.

That token is the point: without it, whoever reaches the page first is the admin of your machine. With it, claiming the instance takes access to the host, which is what it always meant to require. The file is deleted the moment setup succeeds.

The first account is an admin, and it is the only account setup ever makes. Everyone after is invited from Account → Users.

Give it a domain

Point a name at the server's IP and set it in Settings. From then on the instance answers there over HTTPS, and so does everything you deploy.

You do not need one to start. With no domain, Cubeship gives itself an sslip.io address — a name that resolves to your IP without anything being registered anywhere — so certificates and app names work on a box you set up five minutes ago.

Deploy an app

Apps live in an environment inside a project: myproject/production/api is the whole of an app's name, and it is also its path in the registry.

Create one from the dashboard, or:

cubeship app create api --project myproject

An app is created empty and deploys anyway. It has no name on the internet until you give it one, which is a normal thing to be: a worker or a queue consumer has no business answering the internet.

Push an image

The push is the deploy. Nothing else to press:

docker login registry.example.com          # your API key as the password
docker push registry.example.com/myproject/production/api:latest

cubeship app get api prints the exact path to push to.

Run an image from somewhere else

Docker Hub, GHCR, DigitalOcean, ECR — anything you already publish to. Nothing tells Cubeship when you push there, so a deploy is something you ask for:

cubeship app create api --project myproject --source external --image nginx
cubeship app deploy api --tag 1.27

The image is given without a tag: which tag runs is a deploy's argument, so an app pinned to one could never be told to run another.

This is the one that needs nothing configured at all: no domain, no certificate, no registry. It works the minute the installer finishes.

Build from a Git repository

Cubeship builds it on the box, from a Dockerfile you wrote or — with no Dockerfile at all — by reading the code and working the build out. Connect a GitHub account in Git providers and a push deploys it.

Give it a name on the internet

Add a domain to the app and point a DNS record at the server. Cubeship gets the certificate. Under an sslip.io address it already resolves, so the name works the moment you add it.

An app can answer at several names, and each name carries its own port — one image exposing an API and an admin panel is two names.

Reach it from another app

Inside the instance every app answers at cubeship-<project>-<environment>-<app>, on the port it listens on. It is on the app's Network tab, and it is the address to put in another app's environment: it survives deploys, spreads over every copy of a scaled app, and means the same thing from every machine in the cluster — so a worker with no domain at all is still reachable.

The public name cannot do that job from inside the box. The request leaves for a DNS record pointing back at the same machine, and a host that does not route its own traffic back in answers nothing, which reads as the other app being down.

Databases and storage

A database belongs to the instance, not to a project: on one box the usual shape is a single Postgres serving several small apps, and those apps are routinely in different projects.

cubeship db create pg --engine postgres
cubeship db attach pg --app myproject/production/api

The app receives DATABASE_URL and its parts in its own environment from its next deploy — nothing to copy, and no credential passing through your hands. A second database on one app takes a prefix.

Object storage works the same way: a MinIO Cubeship runs, or an S3 bucket you already have. An attached app gets S3_ENDPOINT, S3_BUCKET, the keys and the rest.

Backups

Postgres, MySQL, MariaDB and MongoDB are dumped the way their own tools do it, on a schedule or on demand, from the Backups tab of the database's settings.

  • A time of day, a timezone, and how many to keep. Retention counts only the dumps that worked: a week of failures never pushes out the last good one.
  • Into a bucket, off the machine. Link an object store and the dump streams straight into it without landing on this box's disk — a database larger than the disk under it is still a backup. With nothing linked it is written locally, which is a copy on the same disk as the database, and every screen showing one says so.
  • Restored from the same screen, into the database it came from, after typing that database's name.
  • Kept when the database is deleted, which is the moment they matter. Backups under Platform says what every database is covered by, worst first, and holds the dumps whose database is gone.

Redis is not in the list, on purpose: it is a cache and a queue on a box this size, and there is no good answer to what a nightly copy of one would be restored to.

The instance backs itself up the same way — its own Postgres, with every account, project, app, credential and attachment in it, plus the certificate store and the project pictures — under Settings → Backups. There is no button to put that one back, because the thing it would replace is the database the button runs on: restoring an instance is a fresh install, the daemon stopped, the dump loaded and the files put in place, and the screen says so.

More machines

Add a server, and the instance becomes a cluster:

cubeship server add eu-1

It prints the command to run on the new box, address and credential already in it. The new machine dials home and nothing dials it: it publishes no port to the internet, serves no dashboard and needs no certificate of its own.

The machines share a private network, encrypted, and container names mean the same thing on every one of them. That network is the part that needs them to reach each other: Cubeship opens the three cluster ports on each machine, restricted to the other machines' addresses. So a box behind NAT can report in and be told what to run, and cannot be on that network — which is what an app on it would need to reach a database on another machine.

Every name still arrives at your instance, whichever machine runs the app. One DNS record, one certificate store, and moving an app between machines touches neither.

cubeship app place api --on eu-1               # move it
cubeship app place api --replicas 4            # four copies, spread
cubeship app place api --everywhere            # and on every machine that joins

Scaling takes effect at once, in both directions.

Limits and autoscaling

Cap what one copy of an app — or a database, or a store — may take:

cubeship app limits api --cpu 1 --memory 512Mi

Changing a limit restarts nothing. It is the one part of a running container Docker can change, so raising an app's memory is a request rather than a redeploy.

Or hand the replica count over entirely:

cubeship app autoscale api --min 2 --max 8 --cpu 70

It works from the average CPU across the app's copies — the number on the app's own chart, where 100 is one core. A maximum is required: without one, a loop of requests is a loop of replicas.

The CLI

cubeship talks to the instance's API from your machine.

cubeship login https://cubeship.example.com <your-api-key>
cubeship app list
cubeship app logs api
cubeship app env set api DATABASE_POOL=10

Make an API key under Account in the dashboard. Every noun has --help, and every command it can run is something the dashboard can too — they are the same API.

Installing it. It is one static binary, attached to every release for macOS and Linux, Intel and ARM:

curl -fsSL https://github.com/cubeshipd/cubeship/releases/latest/download/cubeship_darwin_arm64.tar.gz | tar -xz
sudo mv cubeship /usr/local/bin/

Swap darwin_arm64 for darwin_amd64, linux_amd64 or linux_arm64. That name carries no version, so the command keeps working after the next release; the same tarball is also attached under its version — which is what to ask for when you want a specific one — and checksums.txt is beside them.

There is no go install: this module is named cubeship rather than the path it lives at, and a downloaded binary covers the same ground.

MCP

https://your-instance/mcp is the same API for an agent, authenticated with the same key. Point Claude Code, Cursor or whatever you run at it:

{
  "mcpServers": {
    "cubeship": {
      "type": "http",
      "url": "https://cubeship.example.com/mcp",
      "headers": { "Authorization": "Bearer <your-api-key>" }
    }
  }
}

From there it can create a project, deploy an app, read the logs and how the last deploy ended, attach a database and set the variables — the work of the dashboard, without the dashboard. It is stateless on purpose: every call carries the key, so nothing an agent opened can be picked up by another.

Two lines are deliberately not crossed: no tool reads a secret, and no tool sets a container's ceiling. Those stay with a person.

Upgrading

Run the installer again. It pulls the newest release and replaces the containers; nothing under the data directory is touched.

curl -fsSL https://cubeship.dev/install.sh | sh

The dashboard shows you what changed the next time you open it. What is in each release is in the changelog.

Uninstalling

sudo ./uninstall.sh            # the containers; your data is kept
sudo ./uninstall.sh --purge    # and the data, permanently

The default is deliberately not the destructive one: removing the software is not the same as asking to lose your database. Installing again brings the same instance back.

What reaches the internet

There is no telemetry. Nothing reports what you deploy, how much you deploy, or that this instance exists. There is no analytics in the dashboard and no account with anybody.

Three things leave the box, and all three are yours to look at:

  • Installing and upgrading pulls two images from ghcr.io, and asks GitHub which release is newest so it can pin an exact one.
  • The running daemon asks GitHub which releases exist — when an admin opens the update screen, and when the automatic update timer comes round. It is a plain GET on the public releases API with no credential and nothing about your instance in it (the whole of it). An instance that cannot reach GitHub simply never offers an update and says so, rather than claiming to be current.
  • Let's Encrypt, for certificates — and only once you have given the instance a domain.

After that it does what you ask it to: pull the images you named, clone the repositories you connected, write the DNS records you configured.

A build with no version stamped on it — which is what make dev is — never checks for a release at all.

Everything else

  • API referencehttps://your-instance/docs, and the OpenAPI document at /openapi.json.
  • Working on Cubeship itselfCONTRIBUTING.md.
  • Found a security problem? SECURITY.md — report it privately rather than in an issue.

License

Apache-2.0. Use it, run it, modify it, run your company on it, sell what you build on top of it — the same terms Coolify is under, and a patent grant comes with it.

The name is the one thing that is not in the grant — Apache-2.0 section 6 excludes trademarks, deliberately — and TRADEMARK.md says what that means: a public fork gets its own name.

About

A PaaS you run on your own server. docker push, and it is live — with HTTPS, a database beside it, and a second machine when one stops being enough.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages