Cloud

Install Coolify on Ubuntu 26.04 LTS (Self-Hosted PaaS)

If you followed the first version of this guide, your Coolify box probably still runs a beta. That beta cannot take the October 2026 security fixes with the patch script, so we rebuilt the whole install coolify ubuntu 26.04 walkthrough on the current stable line, and two things from the old page had to go: the pinned beta, and the Nginx layer in front of the dashboard, which fights the Traefik proxy that Coolify already runs on ports 80 and 443.

Original content from computingforgeeks.com - post 167056

This guide installs Coolify on a fresh Ubuntu 26.04 LTS server, deploys an app and a PostgreSQL 18 database, moves the dashboard to its own domain, then covers updates, security patches and what breaks when you come from the beta. We replayed the patch and upgrade paths on two extra VMs. I ran through this on Ubuntu 26.04 and 24.04 in October 2026 with Coolify 4.5.0, and it works end to end, except the Let’s Encrypt step, which needs a server with a public IP.

How Coolify fits together on one server

What does the finished lab box look like? Every request from a browser or a CI job enters through the Traefik proxy that Coolify manages, which routes the dashboard to the coolify container and each app to its own container on the same Docker network:

Coolify architecture diagram on Ubuntu 26.04: Traefik proxy on 80 and 443 in front of the coolify container, coolify-db, coolify-redis, an example Node.js app and PostgreSQL 18

We drew the snag in the corner because it surprised us most: a host firewall rule does not close a port that Docker publishes, so the dashboard port needs its own fix in Step 6.

Prerequisites

Coolify wants a server that runs nothing else, because the installer puts Docker Engine on it and starts its own Traefik proxy on ports 80 and 443. An existing Nginx, Apache or Docker setup will collide with it. The installation docs still list Ubuntu LTS releases only up to 24.04, but 26.04 installed with the same script and no changes. Before you start, have these ready.

  • A fresh Ubuntu 26.04 or 24.04 LTS server, amd64 or arm64
  • A user with sudo rights and SSH access
  • A domain or subdomain for the dashboard, with an A record pointing at the server
  • Ports 22, 80 and 443 reachable, plus 8000 for the first login only
  • No Docker from snap: remove it with sudo snap remove docker if it is there

Sizing is where most people get it wrong. The Coolify docs list 2 CPU cores, 2 GB of RAM and 10 GB of disk as the minimum, but the 4.5 installer itself warned us on a 20 GB disk that it wants 30 GB in total and 20 GB free. What actually drives the numbers is the builds: Coolify builds your images on the same server unless you add a build server, so RAM and CPU need room for a BuildKit build while your apps and databases keep running. Our lab VM had 4 vCPU, 8 GB of RAM and a 20 GB disk, and with one app, one PostgreSQL database and Coolify itself running, memory peaked at about 1.6 GB. Treat that as a floor for following along. For a box that builds a few apps a day, start at 4 vCPU, 8 GB and 40 GB or more of disk, because images, build cache and database backups all land on that disk.

Run the post-install baseline for Ubuntu 26.04 server first if the box is brand new, and set up SSH key logins before you open anything to the internet.

Step 1: Add swap before the first build

Ubuntu cloud images ship without swap, and a BuildKit build that runs out of memory gets killed halfway with very little explanation in the log. A 4 GB swap file gives the build somewhere to go when it spikes. Create and enable it, then make it permanent.

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h

On the 8 GB lab VM the Swap line now read 4.0Gi.

               total        used        free      shared  buff/cache   available
Mem:           7.8Gi       602Mi       5.2Gi       1.0Mi       2.2Gi       7.2Gi
Swap:          4.0Gi          0B       4.0Gi

If the total is still 0B, the swapon line failed, usually because the file already exists from an earlier attempt. Run sudo swapon --show to see what is active before you try again.

Step 2: Install Coolify on Ubuntu 26.04 with the official script

The installer takes an optional version argument, and we pass one on purpose. Pinning the version means you know exactly what you got, and the same command reproduces the box later. Read the current stable release from the Coolify version manifest instead of typing a number by hand.

COOLIFY_VERSION=$(curl -fsSL https://cdn.coollabs.io/coolify/versions.json | python3 -c 'import json,sys; print(json.load(sys.stdin)["coolify"]["v4"]["version"])')
echo "${COOLIFY_VERSION}"

It printed the stable 4.x release on our run. The same manifest also carries a nightly release candidate, which is why the command reads the v4 key and nothing else:

4.5.0

Now run the installer with that version. On a fresh box it works through four jobs, in order:

  1. Installs Docker Engine from the Docker repository
  2. Sets log rotation and address pools
  3. Downloads the compose files to /data/coolify/source
  4. Pulls the images and starts Coolify

Pipe it to sudo, because it writes to /data and installs packages.

curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash -s "${COOLIFY_VERSION}"

On the 26.04 lab VM the whole run took 2 minutes 34 seconds, most of it pulling images. Note the two disk warnings at the top: the installer only warns, it does not stop, so a small disk will install fine and fill up later:

  Welcome to Coolify Installer!
  This script will install everything for you. Sit back and relax.
  Log file: /data/coolify/source/installation-20261026-041607.log

       ! Insufficient total disk space: 19GB (required: 30GB)
       ! Insufficient available disk space: 12GB (required: 20GB)
  1/9  Installing required packages  already installed                    0.0s ✓
  2/9  Checking OpenSSH server configuration                              0.1s ✓
  3/9  Installing Docker Engine  v29.9.2                                 31.8s ✓
  4/9  Configuring Docker daemon  log rotation · address pools            2.1s ✓
  5/9  Downloading required files from CDN                                1.2s ✓
  6/9  Setting up environment variables                                   0.1s ✓
  7/9  Checking SSH key for localhost access  generated new key           0.1s ✓
  8/9  Pulling images  coolify · postgres · redis · helper              1m 25s ✓
  9/9  Starting Coolify  v4.5.0                                          28.2s ✓

  Coolify is ready!  Installed in 2m 34s

  Public IPv4   http://203.0.113.10:8000
  Private IP    http://10.0.0.1:8000
                http://10.0.1.1:8000
                http://fd95:74dc:db7f::1:8000

  ! Back up /data/coolify/source/.env to a safe place outside this server (e.g. a password manager).
  Log file: /data/coolify/source/installation-20261026-041607.log

Do not trust the Private IP lines on a homelab box. Those addresses are Docker bridge gateways, not your LAN address, and the Public IPv4 line is your router when the server sits behind NAT. Use the server IP you already SSH to. The same install on Ubuntu 24.04.4 finished in 2 minutes 51 seconds with the same Docker 29.9.2, so nothing in this guide changes between the two LTS releases.

Check that the stack is up. Right after the installer finishes you may see only coolify, coolify-db and coolify-redis; the proxy, Sentinel and the error-page helper follow within a minute or two. The separate coolify-realtime container that older guides mention is gone since the 4.4 line, because realtime now runs inside the main coolify container:

sudo docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"

About a minute after the install, all six were there and everything except the busybox error-page helper reported healthy.

NAMES                       IMAGE                       STATUS
coolify-proxy-error-pages   busybox:musl                Up 6 seconds
coolify-proxy               traefik:v3.7                Up 21 seconds (healthy)
coolify-sentinel            coollabsio/sentinel:1.1.2   Up 23 seconds (healthy)
coolify                     coollabsio/coolify:4.5.0    Up 58 seconds (healthy)
coolify-db                  postgres:15-alpine          Up About a minute (healthy)
coolify-redis               redis:7-alpine              Up About a minute (healthy)

The screenshot shows the same check on the lab box, together with the Docker version the installer put on Ubuntu 26.04 and the test app we deploy later in Step 7:

Coolify containers healthy after install on Ubuntu 26.04 with Docker 29.9.2

If coolify stays at health: starting for more than two minutes, jump to the troubleshooting section at the end, because that is the exact symptom of a version and compose-file mismatch.

Step 3: Create the root account and back up APP_KEY

Do this straight away, because until the first account exists the registration page is open to anyone who can reach port 8000, and the first person to register becomes the root user with root access to your server. Open http://10.0.1.50:8000 (your server IP) and fill in the form. The password needs at least 8 characters with upper case, lower case, a number and a symbol:

Coolify root account setup form on a fresh Ubuntu 26.04 install

The onboarding that follows asks where to deploy. Pick This machine for a single-server setup, then let it create “My first project” with a production environment. If you would rather never expose the form at all, the installer accepts ROOT_USERNAME, ROOT_USER_EMAIL and ROOT_USER_PASSWORD and creates the account for you. Pass them with sudo -E env ROOT_USERNAME=... bash -c 'curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash' as the installation docs show, because a plain export does not survive sudo. We used the form in this lab, not the variables.

Next, copy /data/coolify/source/.env off the server. It holds APP_KEY, which encrypts every secret Coolify stores in its database, so a database backup without this file cannot be restored. The directory is mode 700 and owned by UID 9999, so make a copy your own user can read:

sudo install -m 600 -o "$USER" -g "$USER" /data/coolify/source/.env ~/coolify-env-$(date +%F).env
ls -l ~/coolify-env-*.env

Only you can read the copy.

-rw------- 1 kibet kibet 702 Oct 26 05:05 /home/kibet/coolify-env-2026-10-26.env

Pull it to your workstation with scp '[email protected]:coolify-env-*.env' . (the quotes stop zsh on macOS from expanding the pattern locally), put it in your password manager, then delete the copy on the server with rm ~/coolify-env-*.env. A second copy of the key on the same disk protects you from nothing.

Step 4: Check the proxy status and tour the new dashboard

The first thing we saw after onboarding was a yellow Attention required badge on the localhost server, with the proxy listed as Exited, even though docker ps showed coolify-proxy healthy. The status had simply not been refreshed since the install. Open Servers > localhost, click the status badge and hit the refresh icon next to System status. It flips to Ready with the proxy Running:

Coolify server status showing Traefik proxy running and Sentinel in sync

Do not click Start Proxy while it says Exited. The container is already bound to 80 and 443, and a second start only adds noise to the proxy logs.

The interface was redesigned in the 4.3 line and has kept growing since, so screenshots from older guides no longer match. The dashboard now has a Canvas view (top right of the Projects list, still marked Beta) that draws each server with its proxy, projects and resources, and shows recent deployments at the bottom. Once you have an app and a database, it looks like this:

Coolify Canvas view with a Railpack app and PostgreSQL on the localhost server

Under Monitoring > Metrics you get a fleet page with CPU, memory, network and load per server and the busiest containers. It is empty until you click Enable Metrics On Localhost, which starts collection through the Sentinel container; our charts filled in within a few minutes:

Coolify fleet Metrics page with CPU memory and network for one server

The disk tile stayed at 0% with “No data for this range” during our test, so keep watching disk with df -h for now. Memory is the useful number on a single box: it tells you how much headroom is left before a build starts swapping.

Step 5: Serve the dashboard on its own domain over HTTPS

You do not need Nginx or certbot for the dashboard. Coolify writes a Traefik router for its own domain and asks Let’s Encrypt for the certificate through the HTTP-01 challenge, using the same proxy that serves your apps. Point an A record such as coolify.example.com at the server, keep port 80 open, then go to Settings > General, enter https://coolify.example.com in the URL field and save:

Coolify Settings General with instance URL and HTTP to HTTPS redirect

Redirect HTTP to HTTPS is on by default. Within a few seconds Traefik answers on the new name, and a plain HTTP request is redirected:

curl -sI http://coolify.example.com/

Our lab box used a sslip.io name that resolves to its LAN address, which is why the host below looks odd:

HTTP/1.1 307 Temporary Redirect
Location: https://coolify.10.0.1.50.sslip.io/
Date: Mon, 26 Oct 2026 04:53:43 GMT
Content-Length: 18

That name points at a private IP, so Let’s Encrypt could not reach the server and the certificate request failed. We kept the failure because it is what you will see on any box the internet cannot reach: Traefik serves its own self-signed TRAEFIK DEFAULT CERT and logs the reason. On a server with a public IP and a correct A record the request succeeds; we did not have one for this run, so that last step is the part of the guide we could not retest. Check which certificate is being served:

echo | openssl s_client -connect coolify.example.com:443 -servername coolify.example.com 2>/dev/null | openssl x509 -noout -subject -issuer

A Let’s Encrypt issuer means you are done. This is what the failed case looks like:

subject=CN=TRAEFIK DEFAULT CERT
issuer=CN=TRAEFIK DEFAULT CERT

The fix for that case is in the troubleshooting section, with the exact log line Traefik prints.

No public IP? Put a Cloudflare tunnel in front

A homelab box behind NAT cannot pass HTTP-01. Do not confuse the two Cloudflare features here: the Cloudflare tunnel setup added in the 4.5 release connects a remote server to Coolify over SSH, it does not publish the dashboard or your apps. For that you run a cloudflared tunnel with a tunnel token in front of all resources, give the resources https:// domains, turn off Redirect HTTP to HTTPS on each resource, and let Cloudflare terminate TLS while the tunnel talks plain HTTP to the proxy. It needs a domain whose DNS Cloudflare manages (the redirect setting is what prevents a redirect loop). That guide covers the resources you deploy; putting the dashboard itself behind the same tunnel is not described there, and we did not run any of this path in the lab, so follow the Coolify Cloudflare tunnel guide for the exact steps.

Step 6: Close port 8000 (UFW alone does not do it)

Once the dashboard works on its domain, three host ports become a second, unencrypted way in.

  • 8000 for the dashboard
  • 6001 for realtime updates
  • 6002 for the web terminal

The coolify-proxy container also publishes 8080, the Traefik API port; with the insecure API off there is little behind it, but you will see it in ss -tlnp, and the same firewall problem applies. The obvious fix does not work. We allowed only SSH, 80 and 443 in UFW and enabled it.

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

Then we tested port 8000 from another machine on the LAN. Did the firewall close it? No: the dashboard still answered with a 302, because Docker publishes container ports through its own iptables chain, which UFW never sees.

curl -s -o /dev/null -w '%{http_code}\n' http://10.0.1.50:8000/

So the firewall did nothing for this port.

302

Only do this after Step 5 works, because without a working domain you lock yourself out of the UI. Traefik reaches the dashboard over the Docker network as coolify:8080, coolify:6001 and coolify:6002, so the host bindings are only there for direct IP access. Coolify supports a docker-compose.custom.yml override that survives upgrades, and both upgrade.sh and patch.sh include it. Create it:

sudo vim /data/coolify/source/docker-compose.custom.yml

Add this content. The !override tag makes Compose replace the port list instead of merging it with the default one, which would leave the public bindings in place:

services:
  coolify:
    ports: !override
      - "127.0.0.1:8000:8080"
      - "127.0.0.1:6001:6001"
      - "127.0.0.1:6002:6002"

Recreate only the coolify container with the override applied. Use the full paths, because a sudo user cannot cd into the mode 700 source directory:

S=/data/coolify/source
sudo docker compose --env-file $S/.env -f $S/docker-compose.yml -f $S/docker-compose.prod.yml -f $S/docker-compose.custom.yml up -d --no-deps coolify
sudo docker port coolify

After about 15 seconds the container was healthy again and every port was bound to localhost only:

6001/tcp -> 127.0.0.1:6001
6002/tcp -> 127.0.0.1:6002
8080/tcp -> 127.0.0.1:8000

From the LAN, the same curl to port 8000 now timed out, while the dashboard on its domain still loaded and the realtime connection in the browser reported connected. If you do lock yourself out, the dashboard is still reachable through SSH: run ssh -L 8000:127.0.0.1:8000 [email protected] on your workstation and open http://localhost:8000. To undo the change for good, delete the override file and run the same docker compose command without the last -f. For the rest of the host firewall, the UFW guide for Ubuntu 26.04 covers rate limiting and logging.

Step 7: Deploy an app from a public Git repository

We used the official coollabsio/coolify-examples repository, which holds one small app per stack. In your project open + New, choose Public Git Repository, paste https://github.com/coollabsio/coolify-examples and click Check Repository. The new Smart Scan reads the repo and fills in the build settings. Set the base directory to /javascript/runtimes/nodejs and leave the port at 3000:

Coolify public Git repository build configuration with Railpack and base directory

The default build pack is now Railpack, not Nixpacks as older guides say. You can still pick Nixpacks, a Dockerfile or Docker Compose from the same list, and a repo with a Dockerfile is a good reason to switch. Click Continue to create the application.

Coolify then generates a sslip.io domain from the instance public IPv4. On a box behind NAT that is your router address, so the domain will not resolve to the server from inside the LAN. Either set Instance public IPv4 in Settings > General to the LAN address, or replace the domain under Domains. We replaced it, and the DNS check turned green:

Coolify application Domains page with an sslip.io domain and DNS matches status

Click Deploy. The deployment log shows the helper container cloning the commit, Railpack generating a build plan, the build itself and a rolling update. Our first build took 2 minutes 5 seconds, almost all of it in the Railpack build:

Coolify deployment log for a Railpack build of the Node.js example app

Test the app from any machine that resolves the domain:

curl -s http://hello.10.0.1.50.sslip.io/

A small JSON body comes back from the Fastify example.

{"hello":"from nodejs #2"}

The application header showed Running (no healthcheck). The example has a /health route, so set it under Healthcheck for your own apps, so Coolify can tell when a new container is actually ready during a rolling update. Two more tabs are worth knowing about. Files is a file browser into the running container, handy for checking what the build actually shipped:

Coolify application file browser listing the files inside the running container

The other is Webhooks, where the deploy webhook for CI lives. Since the 4.2 line that call must be a POST, which is covered in the upgrade section below. For private repositories, pick Git Repository (with GitHub App) or the deploy key option in the same resource list instead.

Step 8: Add a PostgreSQL database and backups

In the same project choose + New > PostgreSQL. The picker recommends PostgreSQL 18 and also offers 17, 16, Supabase PostgreSQL, PostGIS and PGVector images. Pick PostgreSQL 18, then click Start. Coolify generates the password and shows the internal connection string under Access, masked until you click the eye icon:

Coolify PostgreSQL 18 database running with masked credentials

The container name is the resource UUID, which you can copy from the page title. Confirm the server version from the host:

sudo docker exec 9xdy17p49kvovuf015c48urp psql -U postgres -c "select version();"

The postgres:18-alpine image was healthy eight seconds after Start:

                                         version
-----------------------------------------------------------------------------------------
 PostgreSQL 18.6 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
(1 row)

To use it from your own app, copy the internal Postgres URL into the application Environment Variables as DATABASE_URL and redeploy, and keep the database private unless something outside the server needs it. Do not do that with the example app: it serves GET /env, which returns every environment variable, so the database password would be public on its domain. Delete the example once you have seen it deploy.

Backups are not on by default. Open Backups on the database, click + Add, enter a cron expression such as 0 3 * * * and save. Without a validated S3 storage the dumps stay on the same disk, which protects you from a bad migration but not from a dead server:

Coolify PostgreSQL backup schedule with daily cron and S3 storage option

Click Back Up Now once to prove it works, then look for the dump on the host:

sudo find /data/coolify/backups -type f -name '*.dmp'

Our test database was empty, so the dump is tiny, but the path tells you where every database backup lands:

/data/coolify/backups/databases/root-team-0/postgresql-database-9xdy17p49kvovuf015c48urp-9xdy17p49kvovuf015c48urp/pg-dump-postgres-1792989159-4olziq9slzhava1rxtvpl98q.dmp

Add an S3 storage under Storages before you rely on this for anything that matters. The same Backups page can also restore a dump through Import Backup.

Step 9: Lock down sign-in with 2FA, passkeys or OIDC

The Coolify account can deploy, delete and open a root terminal on every connected server, so it deserves more than a password. Open your profile from the bottom-left menu. Two-factor authentication with any TOTP app is under Configure 2FA, and the 4.5 release added passkeys right below it:

Coolify profile page with two-factor authentication and passkeys

Read the passkey text carefully: a passkey sign-in skips two-factor authentication. That is fine for a hardware key, but a passkey synced to a phone that also holds your TOTP app means one stolen device is enough. Set up 2FA first, keep the recovery codes off the server, and add a passkey only from a device you trust. Enroll passkeys only after the final HTTPS domain from Step 5 is in place: the passkey pull request notes that they are hidden when you reach the instance by IP and stop working if the instance domain changes.

For a team, Settings > Authentication has a generic OpenID Connect provider (added in the 4.4 line) next to the GitHub, GitLab, Google, Azure, Authentik and Zitadel presets, with options to block password registration and require verified email. Under Settings > Advanced, check that Registration is disabled, which it was by default on our install, and leave API access disabled until you need it for CI (covered in the beta section below). The server hardening guide covers the host side: SSH, unattended upgrades and Fail2ban.

Add more servers (optional)

Everything above runs on one box. When builds start slowing your apps, the usual next move is a second server, either as a deploy target or as a dedicated build server. Under Servers > + New Server you can connect any host over SSH, or create one directly through the Hetzner, Vultr, DigitalOcean or Hostinger integrations:

Coolify server type picker with SSH and Hetzner Vultr DigitalOcean Hostinger providers

For an SSH host, add a private key under Keys & Tokens first and put its public half in root or a sudo user’s authorized_keys on the target, then validate the server so Coolify installs Docker on it. We only connected the localhost server in this lab. If you are weighing Coolify against a lighter option first, our CapRover install guide covers the main alternative, and the open source hosting control panels roundup covers panels for classic LAMP hosting.

How to update Coolify and apply security patches

There are two ways to move a Coolify instance forward, and they do different jobs. The dashboard update (or the automatic updater) moves you to the latest release. The patch.sh script, published with the 21 October 2026 security releases, keeps you on your current minor line and only swaps the coolify container for the fixed build of that line. Those releases fixed the PHP2Shell flaws (CVE-2026-109618 and CVE-2026-109619) through a PHP update, a Sentinel flaw (CVE-2026-111943), and updated cloudflared. Start by checking what you run:

sudo docker inspect coolify --format "{{.Config.Image}}"
sudo docker exec coolify php -r "echo PHP_VERSION, PHP_EOL;"

We ran this on a second VM installed with an unpatched 4.4 release. The PHP version is the quick tell, because the PHP2Shell fix moved PHP to 8.4.27:

docker.io/coollabsio/coolify:4.4.6
8.4.23

Each stable minor line has its own security fix release. The script reads your version and picks the matching target:

You runpatch.sh moves you to
v4.0.xv4.0.1
v4.1.xv4.1.3
v4.2.xv4.2.1
v4.3.xv4.3.24
v4.4.xv4.4.7
beta, RC or nightlyrefuses (see the beta section below)

Run it as root. It backs up .env, pulls the fixed image, recreates only the coolify container and waits for it to report healthy, so your apps and databases keep running:

curl -fsSL https://cdn.coollabs.io/coolify/patch.sh | sudo bash

Look at the last line, which confirms both the new release and the new PHP build.

  Checking the installation
  ✔ Coolify is installed in /data/coolify/source
  ✔ Coolify 4.4.6 is installed
  ✔ the compose files belong to Coolify 4.4.6

  Patching Coolify 4.4.6 to 4.4.7
  ✔ pulled Coolify 4.4.7
  ✔ backed up .env to /data/coolify/source/.env-2026-10-26-04-47-53
  ✔ restarted the coolify container
  ✔ Coolify 4.4.7 is healthy and runs PHP 8.4.27

The script only recreates the coolify container. The Sentinel fix (Sentinel 1.1.2) arrives because Coolify updates Sentinel on its own, so check it too with sudo docker inspect coolify-sentinel --format "{{.Config.Image}}"; the patched 4.4.7 box and our 4.5 box both ran Sentinel 1.1.2. Running the patch script a second time is harmless; it stops at “Coolify 4.4.7 already includes the security fixes” and exits 0. The release notes still recommend moving up to the 4.4 line or newer, because the newer minor lines carry security fixes that were not backported.

Turn on the backup before update first

The 4.5 release added an automatic backup of the Coolify database before every update, and if that backup fails the update does not start. The catch is that it backs up through the instance backup schedule, and on a fresh install that schedule does not exist yet. Settings > Backup on our new 4.5 install said exactly that:

Coolify Settings Backup page saying instance backup is not configured

Click Configure Backup. That creates a daily schedule for coolify-db and shows the Backup before update switch, which is already Enabled:

Coolify instance backup settings with Backup before update enabled

Our manual Back Up Now took one second and wrote a 473 KB dump to /data/coolify/backups/coolify/coolify-db-hostdockerinternal/. The backup runs inside the instance you are updating from, which has a consequence for older boxes. When we updated a patched 4.4.7 box to 4.5.0 from the dashboard, no backup was taken, because 4.4.7 does not have the feature. It protects updates that start on 4.5 or newer.

Update from the dashboard

Go to Settings > Updates, click Check Now and then Upgrade Now. The dialog warns that any deployment running during the update will fail, so wait for builds to finish:

Coolify Upgrade available dialog from 4.4.7 to 4.5.0

Our update from 4.4.7 to 4.5.0 took 1 minute 37 seconds. Read the breaking changes of the 4.5.0 release before you click, because they hit automation: API tokens now need the deploy ability for scheduled tasks, proxy changes, log drains and server side effects such as resource deletion and database imports, backup execution messages are hidden from tokens without read:sensitive or root, and Compose files with scale or replicas above one are rejected. The log ends in /data/coolify/source/upgrade-*.log with “Coolify upgrade completed successfully”. The same page also shows that automatic updates are Enabled by default with a daily schedule. That is convenient on a homelab box; on a server that runs production builds, we would turn it off and update by hand after reading the release notes on the Coolify releases page.

Coming from the beta or v4.0 and v4.1: what breaks

Stable 4.0 shipped in late April 2026, a few days after the first version of this guide pinned 4.0.0-beta.473. If your box still runs that beta, three things matter: the patch script will not touch it, the first workaround in the release notes leaves the container unhealthy, and the 4.2 line changed two behaviours that break existing automation. Take a database dump and copy .env (Step 3) before you change anything, because a beta host has no backup before update:

sudo docker exec coolify-db pg_dump -U coolify -Fc coolify | sudo tee /root/coolify-db-$(date +%F).dump > /dev/null
sudo sh -c 'ls -l /root/coolify-db-*.dump'

Our small instance gave a dump of about 490 KB.

-rw-r--r-- 1 root root 493157 Oct 26 05:07 /root/coolify-db-2026-10-26.dump

On the beta, patch.sh stops at the version check, as designed:

  Checking the installation
  ✔ Coolify is installed in /data/coolify/source
  ✔ Coolify 4.0.0-beta.473 is installed
  ✖ Coolify 4.0.0-beta.473 has no security fix release, upgrade to the latest version from the dashboard instead

The v4.4.7 release notes still suggest installing 4.0.1 first with install.sh | bash -s 4.0.1 and updating from the dashboard after that. In our replay on 26 October 2026 the 4.0.1 install itself ended with “Coolify container is not healthy after 60s”, so we never got to the dashboard step. The installer always downloads the current compose files, whose health check probes the realtime server on port 6001 inside the coolify container, and the 4.0.1 image does not run it there. A user reported the same thing in the PHP2Shell security issue, and the maintainers confirmed it and changed their advice: a beta host should go straight to 4.4.7 with install.sh | sudo bash -s 4.4.7. One honest caveat: we could not bring up a clean beta.473 with today’s installer either (it fails the same way), so our “beta host” was that half-started install, not a box that had run the beta for months. We went one step further and ran the installer with no version argument, which lands on the latest release:

curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bash

It finished in 1 minute 24 seconds with coolify:4.5.0 healthy, and it kept the existing .env. We compared a hash of the APP_KEY line across the three .env files the installer left behind, and it never changed, so the stored secrets stayed readable:

.env-20261026-043543     APP_KEY sha256: 0f1cb8c2424fbe88
.env-20261026-043907     APP_KEY sha256: 0f1cb8c2424fbe88
.env                     APP_KEY sha256: 0f1cb8c2424fbe88

Once you are on a current release, check the two breaking changes from the 4.2 line, plus one related trap we hit with API tokens:

  • Member is read-only. Members can view resources but can no longer create, deploy, start or stop anything. The invite form still defaults to Member, so promote anyone who deploys to Admin under Team.
  • State-changing API calls are POST-only. That covers /deploy, /disable, server validation and every start, restart and stop endpoint, and a GET now returns 405. The /enable endpoint from that list was removed entirely in 4.5.0.
  • Tokens need the deploy permission. A read-only token gets 403 on /deploy whatever method you use.

The second point is the one that silently breaks CI, because a GitHub Actions step that used curl with no -X flag sends a GET. Enable API access under Settings > Advanced, create a token under Keys & Tokens > API tokens with the Deploy permission, then set these values for your instance:

export COOLIFY_URL="https://coolify.example.com"
export COOLIFY_TOKEN="your-token-here"
export APP_UUID="8pp7dbruicrkbaqaxbb0nq8e"

The application UUID is the last part of the application URL in the dashboard. This is the old style of call that many pipelines still make:

curl -s "${COOLIFY_URL}/api/v1/deploy?uuid=${APP_UUID}" -H "Authorization: Bearer ${COOLIFY_TOKEN}" -w '\nHTTP %{http_code}\n'

On 4.5.0 it fails with a clear message, and the response headers include Allow: POST:

{"message":"This endpoint has changed to a POST request."}
HTTP 405

Add -X POST and the deployment is queued:

curl -s -X POST "${COOLIFY_URL}/api/v1/deploy?uuid=${APP_UUID}" -H "Authorization: Bearer ${COOLIFY_TOKEN}" -w '\nHTTP %{http_code}\n'

The response carries the deployment UUID, which you can poll from CI if you want the job to wait for the build:

{"deployments":[{"message":"Application coolify-examples:main-cn1ggakrs73g1efimrykhxdc deployment queued.","resource_uuid":"8pp7dbruicrkbaqaxbb0nq8e","deployment_uuid":"aishv2jrjwykfpp6qrwnfeib"}]}
HTTP 200

Search your pipelines for api/v1/deploy and fix every call before you upgrade, not after the first failed deploy.

What broke on the lab box, and the fix for each

These are the errors we actually hit across the four lab VMs, with the exact text so you can match them.

“Insufficient total disk space: 19GB (required: 30GB)”

The installer printed this on our 20 GB disk and carried on. It is a warning, not a failure, but it is right: images, build cache and backups grow fast. Give the server 30 GB or more, or prune old images under Servers > Docker Cleanup on a schedule.

“ERROR: Coolify container is not healthy after 60s. Status: starting”

We got this every time we asked the current installer for a release older than the 4.4 line (the beta and 4.0.1). The log showed Horizon and the scheduler running, so the app was alive, but the health check in the current compose files probes the realtime server on port 6001 inside the container, and those versions do not run it there. Install 4.4.7 or later, or run the installer without a version argument to land on the latest release, as shown in the beta section.

“Unable to obtain ACME certificate … no valid A records found”

Traefik logged this when the dashboard domain pointed at a private IP, and served TRAEFIK DEFAULT CERT instead. Read the proxy log to see the reason Let’s Encrypt gave:

sudo docker logs coolify-proxy 2>&1 | grep -iE "acme|error" | tail -5

Ours named the DNS problem directly.

ERR Unable to obtain ACME certificate for domains error="unable to generate a certificate for the domains [coolify.10.0.1.50.sslip.io]: resolver: one or more domains had a problem: [coolify.10.0.1.50.sslip.io: invalid authorization: acme: error: 400 :: urn:ietf:params:acme:error:dns :: no valid A records found for coolify.10.0.1.50.sslip.io; no valid AAAA records found for coolify.10.0.1.50.sslip.io]"

Point the A record at the public IP, make sure port 80 reaches the server (cloud security group and router port forward included), and check for a CAA record that leaves out letsencrypt.org. For a box behind NAT, use a cloudflared tunnel for all resources, as described at the end of Step 5.

Proxy shows “Exited” right after install

The server page showed Attention required and the proxy Exited while the container was healthy. Refresh the system status from the badge, as in Step 4, and it reports Running. Only restart the proxy if sudo docker ps really shows coolify-proxy stopped.

“Missing required permissions: deploy” (HTTP 403)

Our first API token kept only the default Read permission, and both GET and POST to /deploy returned 403 with this message. The permission check runs before the method check, so a 403 tells you nothing about GET versus POST. Create a new token with Deploy ticked.

Generated app domain does not resolve on the LAN

Coolify builds sslip.io names from the detected public IPv4, which is the router on a NAT network. Set Instance public IPv4 to the LAN address in Settings > General, or give each app a real domain, and the DNS check on the Domains page turns green.

Keep reading

Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) Ubuntu Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Ubuntu 26.04 vs 24.04 LTS: Should You Upgrade Now? Ubuntu Ubuntu 26.04 vs 24.04 LTS: Should You Upgrade Now? Upgrade a Single Package on Ubuntu / Debian with apt Debian Upgrade a Single Package on Ubuntu / Debian with apt CapRover Docker Install on Debian 13: Deploy Your First App Containers CapRover Docker Install on Debian 13: Deploy Your First App How To Install Skype on Ubuntu 24.04|22.04|20.04 VOIP How To Install Skype on Ubuntu 24.04|22.04|20.04

Leave a Comment

Press ESC to close