Coolify self-hosted install on a VPS: your own Heroku and Vercel
Coolify self-hosted install on a VPS: official script, dashboard domain with TLS, GitHub deploys, S3 backups and safe updates — your own Heroku and Vercel.
This guide was checked against Coolify 4.3.23 and is current as of September 2026. Coolify v4 only left beta in April 2026, so many 2024–2025 tutorials are out of date.
What Coolify is and when it fits
Coolify is an open-source panel under the Apache 2.0 license that turns a server into a PaaS: on git push it builds an image, starts the container, gets a Let's Encrypt certificate and routes the domain to the app. It also ships databases, backups, preview deployments and a catalog of more than 300 services, including n8n, Uptime Kuma and Forgejo.
Under the hood it is plain Docker: the panel, its PostgreSQL, Redis and the live-updates service run in containers, and every server with apps gets the coolify-proxy container running Traefik or Caddy. Coolify manages every server, including its own, over SSH.
- It fits several apps and databases on one or two servers, when you want push-to-deploy, previews and a shared UI instead of SSH.
- Skip it for a single app that rarely changes: Docker Compose and a Git deploy script are enough.
- Skip it when infrastructure has to live in git: Coolify keeps its settings in its own database, while Kamal 2 or plain compose files keep them in the repository.
How much server you need
The official minimum is 2 cores, 2 GB of RAM and 10 GB of disk. The panel itself is light: in a test on 4.3.23 its containers used about 300 MB at idle, but on a 1 GB server a build of a small Node app took almost all the memory plus more than a gigabyte of swap.
- 2 vCPU and 4 GB — the panel, a database and a few light services, one build at a time.
- 4 vCPU and 8 GB — several Node.js or Python apps with PostgreSQL, built on the same server.
- 40 GB of disk or more: images, build cache and local backups grow faster than your data.
Builds are the main consumer of CPU and memory. In one issue, a Next.js rebuild took 3.6 GB of RAM and froze the panel on an 8 GB server without swap. An app's resource limits apply to the running container only, not to the build. On a small server, set Number of concurrent builds under the server's Configuration → Advanced to 1 instead of 2, add swap, and move frequent builds off production (step 10).
ForestsNet Cloud plans count outbound traffic only: docker pull, git clone and dependency downloads during builds are free, while outbound traffic is what your apps serve, backup uploads and image pushes from a build server. For other developer workloads, see the VPS overview.
What you need
- A VPS with Ubuntu 24.04 or 22.04 LTS or Debian 12, with no web server and no other Docker projects: the proxy takes ports 80 and 443, and Coolify's cleanup deletes images it does not own.
- SSH access as root with a key: from our SSH hardening guide,
PermitRootLogin prohibit-passwordworks,nodoes not. - A domain with A records pointing to the server:
coolify.example.comfor the dashboard and*.apps.example.comfor apps, see pointing a domain and DNS records. - A GitHub repository and S3-compatible storage outside the server.
Step 1. Prepare the server
Update the system following the security checklist. With 4 GB of RAM or less, add swap: it will not speed builds up, but it helps the server survive their peak.
apt update && apt -y upgrade
fallocate -l 4G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstabStep 2. Install Coolify with the official script
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bashThe script installs Docker Engine via get.docker.com (Docker from snap is not supported), writes log rotation and an address pool to /etc/docker/daemon.json, creates /data/coolify with secrets in /data/coolify/source/.env, adds its SSH key to root's authorized_keys and starts the containers.
Pin the version and turn off auto-updates right away (the updates section explains why):
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | AUTOUPDATE=false bash -s 4.3.23Keep a copy of /data/coolify/source/.env off the server: its APP_KEY encrypts the keys and passwords in Coolify's database, and a panel backup is useless without it.
Step 3. Create the admin account and move the dashboard to a domain
Open http://IP:8000 and register right away: the first user gets root access to the server, and registration closes after that. Until then, anyone can claim the page.
Turn on 2FA under Profile, then set URL in Settings → Configuration → General to https://coolify.example.com. Coolify checks DNS and the proxy requests a certificate. Make sure the dashboard, live deployment logs and the terminal all work over HTTPS. Enter internationalized domain names in Punycode (xn--...).
Step 4. Close ports 8000, 6001 and 6002
You cannot close them with ufw: Docker publishes ports with its own iptables rules that bypass it, see our ufw guide. The documented way is to bind the ports to 127.0.0.1 in docker-compose.custom.yml, which updates leave alone:
cat > /data/coolify/source/docker-compose.custom.yml <<'EOF'
services:
coolify:
ports: !override
- "127.0.0.1:${APP_PORT:-8000}:8080"
soketi:
ports: !override
- "127.0.0.1:${SOKETI_PORT:-6001}:6001"
- "127.0.0.1:6002:6002"
EOF
cd /data/coolify/source
docker compose --env-file .env -f docker-compose.yml -f docker-compose.prod.yml \
-f docker-compose.custom.yml config -q && echo OK
curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh | bash -s 4.3.23The last command restarts Coolify with the new file; use your installed version in it. From another machine, curl -m 5 http://IP:8000 should no longer answer.
Step 5. Set up a wildcard domain for apps
Under Servers → localhost → General, set Wildcard Domain to https://apps.example.com: Generate Domain then gives apps addresses like https://<id>.apps.example.com, and your own domain goes into the app's Domains field.
Each subdomain gets its own certificate, and Let's Encrypt issues at most 50 certificates per registered domain every 7 days. With dozens of apps and previews, get one wildcard certificate via DNS-01: in Servers → Proxy → Configuration, replace the httpchallenge flags with dnschallenge and your DNS provider's token, then add a router for *.apps.example.com under Dynamic Configurations.
The sslip.io addresses Coolify generates without a wildcard domain are for HTTP testing only. TLS needs your own domain: on ForestsNet Cloud servers, the domain and certificate must belong to you.
Step 6. Connect GitHub
Connect GitHub after the dashboard is on its domain: a GitHub App stores the webhook address when it is created, and an app created while you were on http://IP:8000 will silently stop triggering deploys once that port is closed.
Under Sources → + Add, choose GitHub App, Automated Installation and the endpoint https://coolify.example.com. Keep Preview Deployments if you want previews, and give the app access to selected repositories only.
Step 7. Deploy an app
In a project, click + New → Private Repository (with GitHub App) and pick the server, repository, branch and Build Pack.
Build method
- Nixpacks detects the language on its own — handy to start with, but Railway no longer develops it.
- Railpack is its successor, in Coolify since 4.1 and still in beta; compare the images before switching.
- Dockerfile is the predictable choice for production: the same image locally, in CI and on the server.
- Docker Compose handles multi-service stacks, but without rolling updates or a build server, and health checks live in the compose file.
- Static serves prebuilt files through nginx, and Docker Image deploys an image from a registry with no build.
Port and listen address
Ports Exposes is the port inside the container, and the proxy sends domain traffic to the first one; a domain like https://app.example.com:3000 routes to port 3000 and opens nothing to the outside. The app must listen on 0.0.0.0, or you get a 502. Avoid Port Mappings for web apps: such a port is exposed to the internet past ufw and disables rolling updates.
Environment variables
Each variable has two independent switches, Build Variable and Runtime Variable, both on by default. Turn off Build Variable for secrets that only the running app needs: build arguments stay in the image metadata. NEXT_PUBLIC_* and VITE_* are the opposite: they are baked in at build time, so changing them needs a redeploy, not a restart.
Health check
Under Configuration → Healthcheck, add a check such as GET /health. It runs inside the container with curl or wget, which Alpine and distroless images often lack. The Return Code and Response Text fields are not compared; only the exit code counts.
Without a check, a rolling update switches traffic as soon as the container starts, even if the app is still booting. With one, the old container keeps serving until the new one is healthy; the app gets 30 seconds to exit on SIGTERM.
Step 8. Databases and backups
+ New → Database creates PostgreSQL, MySQL, MariaDB, MongoDB, Redis, KeyDB, Dragonfly or ClickHouse. Apps reach the database by its internal address on the Docker network, so it needs no public port. For the databases themselves, see our PostgreSQL and Redis guides.
Schedule and S3
Add the bucket under S3 Storages and validate it, then open Backups → + Add in the database: a cron schedule, S3 and retention, set separately for local copies and S3. Built-in backups cover PostgreSQL, MySQL, MariaDB, MongoDB and ClickHouse, but not Redis, KeyDB or Dragonfly. A stopped database is skipped.
Do not take a green status at face value: issues describe buckets that stayed empty for weeks while emails reported success, and after the upgrade to 4.1.2 the backup of the panel itself silently stopped running. Both bugs are fixed, but check the bucket yourself.
Since 4.3, volumes can be backed up too, as tar.gz archives, but there is no restore button, and an archive of a volume that is being written to can be inconsistent.
Test the restore
Once a month, create a throwaway database of the same version, load a fresh dump through Configuration → Import Backup and point a copy of the app at it. Manual dumps are covered in Database backups.
Back up Coolify itself
Projects, variables and keys live in coolify-db; turn on its daily backup under Settings → Backup. Only the dump goes to S3, while restoring on a new server also needs APP_KEY from .env and the /data/coolify/ssh/keys directory, so copy those separately, for example with restic.
Step 9. Turn on preview deployments
Previews are enabled under Configuration → Advanced: Coolify deploys a copy for each pull request and removes it when the PR is closed. The address comes from Preview URL Template, {{pr_id}}.{{domain}} by default: PR #42 of an app on app.example.com gets 42.app.example.com and needs a *.app.example.com record. It is easier to keep all previews under one domain with {{pr_id}}.preview.example.com and the wildcard certificate from step 5.
- Each preview is one more build and one more container, so plan capacity.
- Give previews their own variables and a separate database: PR code runs on your server.
- Keep Allow Public PR Deployments off, or a pull request from any fork can start a preview.
Step 10. Move builds off the production server
It is time when deploys slow production down or end in OOM kills.
GitHub Actions. A workflow builds the image and pushes it to GHCR, and Coolify deploys a Docker Image app when the API is called. Enable API Access under Settings → Configuration → Advanced, create a token with the deploy permission in Keys & Tokens, and call the webhook as the last step, after tests. You can also build on your own self-hosted runner.
curl --fail -X POST "https://coolify.example.com/api/v1/deploy?uuid=<uuid>&force=false" \
-H "Authorization: Bearer $COOLIFY_TOKEN"Build server. A clean server is added under Servers with Use it as a build server? checked and cannot run apps. It needs docker login to your registry, and the app needs Docker Image and Use a Build Server?; for a private registry, log in on production too. Both servers must share a CPU architecture, and Compose apps cannot use this mode. If the build server is unavailable, Coolify builds on production and logs "No suitable build server found".
Multiple servers. Each server runs its own proxy, and an app's DNS must point to its server. One app can be deployed to several servers through a registry and an external load balancer, but not with persistent volumes or Compose. Docker Swarm support is deprecated and will be removed in v5.
Updates and security
Updates
Auto-update is on after installation and runs every night at 00:00, when all instances pull new images at once: in one issue the update hit a registry rate limit 37 nights in a row while the log reported success. Turn auto-update off under Settings → Configuration → Updates and update by hand: read the release notes, back up the instance, make sure no deployment is running, then use the button in the panel or the script with an explicit version:
curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh | bash -s 4.3.23Without a version, the script writes latest to .env and update checks break. Downgrade with the same script and an older version, as long as the newer one did not change the database schema; otherwise restore the instance backup.
What broke in 2026:
- 4.2–4.3: deploy and lifecycle API endpoints accept POST only, so old GET webhooks get a 405; the Member role became read-only; Traefik router names changed for compose services with dots or hyphens in their names.
- Patch releases 4.3.19 and 4.3.22: Sentinel became mandatory, and host paths for volumes were removed from the UI and API.
- Docker 29 with an old Traefik: sites return "No available server" and the proxy logs show
client version 1.24 is too old. Coolify does not update the proxy by itself, so switch the image to Traefik 3.6.1 or newer under Servers → Proxy before upgrading Docker.
Security
Coolify holds SSH keys with root access to every connected server, so a compromised panel means compromised infrastructure. In 2025–2026, 70 security advisories were published for it, 21 of them critical, mostly command injection that gave a panel user root on connected servers. In January 2026, Censys counted about 52,000 dashboards exposed to the internet. Nearly all of this was fixed before 4.0.0, but disclosure came two to five months after the fixes, so lagging behind releases is risky.
The minimum:
- ports 8000, 6001 and 6002 are closed, the dashboard is HTTPS-only, and every account has 2FA;
- the team includes only people you would trust with root; the API is off or limited to tokens with minimal permissions and Allowed IPs;
- databases have no public ports;
- security fixes go in within a couple of days, with releases and advisories on watch;
- SSH is locked down per the checklist with fail2ban, and a changed SSH port is also set in the server's settings in Coolify.
Catalog services need updates too: one Coolify user's Umami instance mined Monero for ten days through a Next.js vulnerability, even though a fix had been out for a week.
Dokploy as an alternative
Dokploy is the closest alternative: a panel on top of Docker and Traefik with databases, S3 backups and previews. It needs at least 2 GB of RAM and 30 GB of disk and takes ports 80, 443 and 3000:
curl -sSL https://dokploy.com/install.sh | sh- Swarm at the core: the installer always initializes Docker Swarm, and apps run as services with replicas.
- Compose-first: compose projects and stacks are the main workflow.
- Still 0.x: the current release is 0.30.8; Dokploy had 60 security advisories in 2025–2026, 34 critical, so do not stay below 0.29.13. SSO and audit logs need a commercial license.
- Port 3000 is closed once the domain works with
docker service update --publish-rm "published=3000,target=3000,mode=host" dokploy, and the Swarm ports 2377, 7946 and 4789 with a firewall.
Dokploy suits you better if you want replicas across nodes and Compose as the main format; Coolify, if a stable 4.x line, a plain Apache 2.0 license and rolling updates without Swarm matter more.
Troubleshooting
- Dashboard does not open on its domain — DNS has not propagated, port 80 is closed or there is no certificate. Get in through the SSH tunnel and check Servers → Proxy → Logs.
- 502 Bad Gateway — the app listens on
127.0.0.1or on a port that does not match Ports Exposes. - 503 "No available server" — the container is unhealthy (the image lacks
curlorwget, or the check hits the wrong port), or Traefik is too old after a Docker upgrade. - 504 on large uploads or long requests — Traefik's read timeout is 60 seconds; raise it in the proxy configuration.
- TRAEFIK DEFAULT CERT in the browser — Let's Encrypt validation failed: port 80 is closed, Cloudflare proxies the domain, an AAAA record leads to a broken IPv6 path, or you hit a rate limit (a 429 in the proxy logs).
- Endless redirects behind Cloudflare — SSL mode is Flexible instead of Full (strict), see Cloudflare in front of your VPS.
- The server freezes during deploys — a build ate the memory and CPU, and limits do not apply to it: one build at a time, swap or a build server. For load analysis, see High load on a VPS.
- Pushes do not trigger deploys — the GitHub App sends webhooks to an old address. Check Recent Deliveries on GitHub and set the Webhook URL to
https://coolify.example.com/webhooks/source/github/events. - Deploys fail with "no space left on device" — images and build cache filled the disk. Enable Servers → Docker Cleanup (by default daily or at 80% disk usage), but not Delete Unused Volumes: unused volumes can still hold data. See also Running out of disk space.
Sources
- Coolify docs: installation, firewall, domains, wildcard certificates, health checks, database backups, instance backup, build servers, updates, security model
- Coolify on GitHub: install.sh, releases, security advisories, CVE-2026-86117
- Coolify issues: Next.js rebuild, empty S3 bucket, coolify-db backup, auto-update, install.sh, Docker 29 and Traefik
- Coolify on 1 GB of RAM, The Hacker News on the vulnerabilities, Umami mining Monero, Let's Encrypt rate limits
- Dokploy: installation, GitHub