Skip to content

How to Deploy CapRover on a VPS

Updated Sep 2026

verified on Ubuntu 26.04 · Sep 2026
We earn commissions when you shop through the links below. Full disclosure →

Install CapRover on an Ubuntu VPS — replace the default captain42 password before anything else, set up wildcard DNS and HTTPS, deploy a first app, and back up both halves of the platform.

Before you start
  • A VPS with at least 1 GB RAM (2 GB+ if you build apps from source on it)
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access, dedicated to CapRover
  • A domain where you can create a wildcard A record
  • Docker Engine installed (see the base guide below)
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What CapRover is

CapRover is a self-hosted platform-as-a-service: a dashboard and CLI on top of Docker Swarm that builds and runs your apps, routes a subdomain to each through its own nginx, and gets Let's Encrypt certificates for them. You deploy from a Git push, a tarball, a Dockerfile or a prebuilt image, and there is a catalogue of one-click apps (databases, WordPress and many others). It is written in TypeScript on Node.js, and the licence is Apache-2.0 with a CapRover-specific appendix.

CapRover takes over the whole box: it owns ports 80, 443 and 3000, turns the server into a Swarm manager, and keeps its state in /captain. Give it a dedicated server rather than installing it next to other things.

If you are choosing a platform, Dokku vs CapRover and CapRover vs Coolify cover the main alternatives, and Dokploy is another Swarm-based option with a more modern UI.

Server sizing

CapRover's docs ask for at least 1 GB of RAM, and add that more memory helps when building applications from source. That is the realistic split:

  • 1 GB runs CapRover plus a few small apps deployed as prebuilt images.
  • 2–4 GB is the comfortable range if the server builds your apps from source, since builds (npm, Maven, Docker multi-stage) are the memory spikes.
  • Disk fills with old image versions. Start with 40 GB+ and turn on the scheduled image cleanup described below.

CapRover publishes AMD64 and ARM64 images, so ARM VPS plans work too.

Prepare the server

This guide assumes Docker Engine is installed. If not, work through Docker & Compose on Ubuntu first — CapRover's docs advise against the snap package of Docker.

Open the ports CapRover needs. 80/tcp and 443/tcp carry app traffic and the Let's Encrypt challenge, 443/udp is for HTTP/3, and 3000/tcp is the initial dashboard port:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw allow 3000/tcp
sudo ufw --force enable
sudo ufw status

Check your provider's own firewall as well; it can block traffic that ufw allows. And keep in mind that ports published by Docker bypass ufw rules, a point CapRover's firewall page makes too — so an app you later map to a public port is reachable whatever ufw says.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install CapRover

The installer is one container. It initialises Docker Swarm, creates the CapRover services and exits:

sudo docker run -p 80:80 -p 443:443 -p 3000:3000 \
  -e ACCEPTED_TERMS=true \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /captain:/captain \
  caprover/caprover

ACCEPTED_TERMS=true accepts CapRover's terms non-interactively. Before it does anything, the installer checks that port 3000 on the server's public IP is reachable; if your provider's firewall blocks it, the install stops with Port timed out: 3000 and suggests the ports to open. Setup continues for a little while after the installer exits; wait for the dashboard's API to answer:

sudo apt-get install -y jq >/dev/null
for i in $(seq 1 60); do
  curl -s http://127.0.0.1:3000/api/v2/login -H 'x-namespace: captain' \
    -H 'content-type: application/json' --data '{"password":"-"}' \
    | jq -e '.status != 1001' >/dev/null 2>&1 && break
  sleep 5
done
sudo docker service ls

On first boot CapRover restarts itself once to load a generated secret, and until it is done the API answers Captain is not ready yet (status 1001) or a plain 500. The loop waits for a real answer, which took under a minute on our test box.

You should see the core Swarm services, including captain-captain (the dashboard and API) and captain-nginx (the router).

Replace the default password — now

A fresh CapRover dashboard is on http://SERVER_IP:3000, over plain HTTP, with the password captain42. Anyone who finds the port before you do owns the server, so change it straight away. You can do it in the dashboard's Settings, or from the shell through CapRover's documented HTTP API, which generates a strong password and saves it somewhere only you can read:

if [ ! -f ~/caprover-password.txt ]; then
  NEWPASS="$(openssl rand -hex 14)"
  TOKEN=$(curl -fsS http://127.0.0.1:3000/api/v2/login \
    -H 'x-namespace: captain' -H 'content-type: application/json' \
    --data '{"password":"captain42"}' | jq -er '.data.token') &&
  curl -fsS http://127.0.0.1:3000/api/v2/user/changepassword/ \
    -H 'x-namespace: captain' -H "x-captain-auth: $TOKEN" -H 'content-type: application/json' \
    --data "$(jq -n --arg o captain42 --arg n "$NEWPASS" '{oldPassword:$o,newPassword:$n}')" \
    | jq -e '.status == 100' >/dev/null &&
  echo "$NEWPASS" > ~/caprover-password.txt && chmod 600 ~/caprover-password.txt &&
  echo "password changed"
fi

Confirm the old password is rejected and the new one works:

curl -s http://127.0.0.1:3000/api/v2/login -H 'x-namespace: captain' \
  -H 'content-type: application/json' --data '{"password":"captain42"}' | jq -r '.description'
curl -s http://127.0.0.1:3000/api/v2/login -H 'x-namespace: captain' \
  -H 'content-type: application/json' \
  --data "$(jq -n --arg p "$(cat ~/caprover-password.txt)" '{password:$p}')" \
  | jq -e '.data.token' >/dev/null && echo "new password: login OK"

The password is 28 characters on purpose. CapRover's login rejects passwords longer than 29 characters ("password is too long"), even though the change-password call accepts longer ones — set a 32-character password and you lock yourself out of the normal login. (The error message says the first 29 characters of a longer password still work.)

The API wraps every reply in its own status field (100 means success), so an HTTP 200 alone does not mean the call worked — which is why the block above checks .status before saving the new password.

HTTPS + domain: wildcard DNS

CapRover gives every app a subdomain of one root domain. If the root is apps.example.com, the dashboard lives at captain.apps.example.com and an app called api at api.apps.example.com. One wildcard record covers them all. At your DNS provider, create:

Type Name (in the example.com zone) Value
A *.apps your server's public IPv4

Check that both captain.apps.example.com and a made-up name such as random123.apps.example.com resolve to the server:

dig +short captain.apps.example.com
dig +short random123.apps.example.com

If your DNS provider offers a proxy mode (Cloudflare's orange cloud), use DNS-only while setting up, so Let's Encrypt reaches the server directly.

Then in the dashboard at http://SERVER_IP:3000, sign in with the new password and:

  1. Set the root domain to apps.example.com — without *. or captain..
  2. Open http://captain.apps.example.com and check you can sign in there.
  3. Enable HTTPS for the dashboard. CapRover requests a certificate for captain.apps.example.com, so ports 80 and 443 and the DNS must already work.
  4. Open https://captain.apps.example.com, confirm the certificate is valid, then turn on Force HTTPS.

Once the dashboard works over HTTPS on its domain, CapRover's docs say you may close public access to port 3000:

sudo ufw delete allow 3000/tcp

Because Docker-published ports bypass ufw, also block 3000 in your provider's firewall to be sure.

Deploy a first app

The quickest end-to-end test uses a prebuilt image. In the dashboard:

  1. Apps → create an app called my-first-app.

  2. Deployment tab → paste this Captain Definition and deploy:

    {
      "schemaVersion": 2,
      "imageName": "nginx:stable-alpine"
    }
  3. In HTTP Settings, make sure the container HTTP port is 80.

  4. Visit http://my-first-app.apps.example.com — the nginx welcome page means DNS, routing and the container all work.

  5. Click Enable HTTPS for the app, then Force HTTPS.

For your own code, commit a captain-definition file (pointing at your Dockerfile) and deploy with caprover deploy from the CLI (npm install -g caprover) or connect the app to a Git repository with a webhook. Deploys upload the committed branch, so uncommitted and .gitignored files are left out.

Stateful apps need persistent directories set in the app config before they hold real data; without one, the data lives in the container and is lost on the next deploy.

Securing it

  • The password change above is the big one. Also enable HTTPS and Force HTTPS for the dashboard, then close port 3000.
  • Treat anything with Docker socket access as root on the host. CapRover's own security page says as much: whoever holds the dashboard can run any container.
  • Use per-app deployment tokens for CI rather than the dashboard password.
  • Do not expose database one-click apps on public ports. Apps reach each other over the internal Swarm network as srv-captain--<app-name>.

Backups

A CapRover backup and your app data are two separate things, and you need both:

  1. CapRover's configuration: Settings → Create Backup, then download the .tar. This holds CapRover's own state and settings, not the contents of your apps' volumes. The download link is temporary; keep your own copy off the server.
  2. App data: dump each database with its own tool and archive each persistent directory. Find the volumes with:
docker volume ls
sudo du -sh /captain

To restore on a fresh server, copy the archive to /captain/backup.tar before running the installer — it detects the file at startup — then point DNS at the new box and restore each app's data.

Upgrades

Download a CapRover backup and back up app data first. Then in Settings, the dashboard shows the current and available versions with a changelog; use its update button. It pulls the new image and updates the captain-captain service. If something goes wrong:

docker service ps captain-captain --no-trunc
docker service logs captain-captain --tail 100

Keep the host itself patched too (sudo apt upgrade, Docker included).

Troubleshooting

The dashboard on port 3000 does not load. Check sudo docker service ls shows captain-captain running 1/1, and that port 3000 is open in both ufw and the provider firewall.

Enabling HTTPS fails. Let's Encrypt must reach the name on port 80. Check the wildcard record resolves to this server, that no DNS proxy is in the way, and that 80/443 are open in the provider firewall.

The app shows a 502. The container HTTP port in HTTP Settings does not match the port the app listens on, or the app crashed; check its logs in the dashboard.

Builds fail on a small server. Usually memory. Check free -h and the build log. Upstream suggests a swap file to get a build through, but building the image in CI and deploying the image is the lasting fix.

The disk is filling up. Every deploy keeps an image. Under Settings → disk cleanup, set how many recent versions per app to keep and a cron schedule. It only removes images, never volumes. Avoid docker system prune --all or volume pruning on a CapRover host — it can delete what rollbacks or stopped apps still need.

Verification + next steps

You're done when: the dashboard loads at https://captain.apps.example.com with a valid certificate, captain42 no longer works, port 3000 is closed, and https://my-first-app.apps.example.com serves the test page.

From there, deploy your real apps, set persistent directories before they store anything, and schedule both backups. For hosting picks, see Best VPS for Docker and Best VPS for Node.js.

Next steps

How to self-host CapRover →More self-hosted platform-as-a-service tools →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.