← Back to Blog

Self-Host Coolify: Git Deploys, TLS, and Scheduled Backups on One VPS

You rent a VPS. Every new project still starts the same way: another Compose file, another reverse proxy block, another cron job that dumps Postgres into a folder nobody checks. Managed platforms removed that work, then charged you per seat and per build minute, and put your deploy pipeline inside someone else's account.

Coolify is a control plane you install on your own server. It clones your repository, builds a container, routes a domain to it, issues the certificate, provisions databases, and runs scheduled backups. This guide walks the whole path from install to restore test. Every command, menu path, and number here comes from Coolify's own documentation, linked at the bottom.

Prerequisites

  • A Linux server with root SSH access. A fresh box is better than a box that already runs other things.
  • The documented floor is 2 CPU cores, 2 GB RAM, and 10 GB of disk. That is Coolify itself plus a small app or two, not a busy production server.
  • Ubuntu LTS (20.04, 22.04, or 24.04) or Debian. Coolify also runs on Arch, Alpine, and 64-bit Raspberry Pi OS. Non-LTS Ubuntu releases should use the manual install path.
  • Ports 22, 80, and 443 open. Port 8000 stays open only until you move the dashboard to a domain.
  • Docker should not come from snap. If it does, remove it and let the installer use Docker's official packages.
  • A domain you can point at the server, if you want HTTPS on your own hostname. For a smoke test you can use the sslip.io domain Coolify generates from the server IP.

Step 1: install

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

The script installs Docker Engine 24 or newer if it is missing, adjusts Docker daemon and logging settings, creates the data directory at /data/coolify, generates the SSH keys Coolify uses to manage servers, then starts the Coolify containers plus the reverse proxy. When it finishes, the terminal prints a dashboard link on port 8000.

Open it in a browser: http://SERVER_IP:8000

Step 2: two things before anything else

Create the admin account. Until the first account exists, the registration page is open, and whoever registers first becomes the instance admin with access to the host. The docs call this a real security step, not a formality, because it is one.

Then copy /data/coolify/source/.env off the server. That file holds APP_KEY, which encrypts the values in Coolify's own database. Restore an instance backup without it and your stored secrets will not decrypt.

If you would rather never expose that registration window, create the root user at install time instead:

sudo -E env ROOT_USERNAME=RootUser [email protected] ROOT_USER_PASSWORD='a-strong-password' bash -c 'curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash'

Step 3: firewall

ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 8000/tcp
ufw enable
ufw status verbose

Port 8000 is temporary. Once the dashboard is reachable over HTTPS on your domain, close it.

One detail from Coolify's troubleshooting pages: ufw LIMIT rules on port 22 can make the server connection look unstable, because the connection is SSH-based and rate limiting interferes with it. Plain allow rules for SSH are the safer choice.

Step 4: dashboard domain and TLS

Point an A record at the server first, for example coolify.example.com. Then open Settings, go to Configuration, then General, and set URL to https://coolify.example.com.

For the certificate to issue, three things have to be true: the hostname resolves to this server, ports 80 and 443 reach the Coolify proxy, and the domain is saved in Coolify. How Coolify decides this has nothing to do with the preset code, it simply writes an HTTPS route and the proxy requests the certificate.

Step 5: prove the pipeline with a throwaway deploy

Before connecting a repository, deploy something that cannot fail for code reasons.

Create a project, select Create New Resource, choose Docker Image, and enter nginx:alpine. Set Ports Exposes to 80. Deploy.

Coolify creates a test domain that looks like http://UUID.SERVER_IP.sslip.io. Open it. When the Nginx welcome page loads, four layers have been proven at once: the server, the Docker daemon, the proxy, and domain routing. If the page does not load, check the deployment logs and confirm Ports Exposes is 80, which is the most common cause of a first deploy that "works" but serves nothing.

Step 6: deploy a real application from Git

Create a new resource from a public repository URL, a deploy key, or a GitHub App for private repositories. GitHub, GitLab, Bitbucket, and Gitea are all supported.

Pick a build pack:

  • Nixpacks builds an image by detecting your stack and generating a Dockerfile for you. Railpack is its successor and behaves the same way.
  • Static serves pre-built assets through Nginx, which is what you want for an Astro, Vite, or other generated site.
  • Dockerfile and Docker Compose hand the build to files you already maintain. Use these when a generated Dockerfile keeps guessing wrong.

Then set the domain in the Domains field, as a full URL: https://app.example.com. Without a port, Coolify routes to port 80 inside the container. If your app listens on 3000, write https://app.example.com:3000 and it sends traffic to the container port instead.

Environment variables live under the resource's Environment Variables. For a Docker Compose resource, a reference in the Compose file such as ${DATABASE_URL} makes Coolify create an editable field for it, so the value stays out of the repository. The operators are worth knowing: ${VAR:-default} pre-fills a value, and ${VAR:?} marks it required, which blocks deployment until you fill it in.

Automatic deployments come from the Git integration. Connect through a GitHub App, then open Configuration, Advanced, Deployment & Git, and enable Auto Deploy. Pushes to the configured branch trigger a deployment. In a monorepo, set Watch Paths under Configuration, General, Build to something like apps/api/** or packages/shared/** so a documentation-only commit stops rebuilding your API.

Rollbacks are not a separate feature. Every run appears in Deployments, and redeploying an earlier commit is a button.

Step 7: database, then backups

Coolify provisions Postgres, MySQL, MariaDB, MongoDB, and ClickHouse as one-click resources with generated credentials. Two operational details matter more than the setup.

First, your application talks to the database over the internal Docker network. The container port is not published, and it should stay that way. Only clients outside the server need the public port enabled, and that is a deliberate switch, not a default.

Second, backups are good enough to actually use. Open the database, go to Backups, and add a schedule. The schedule is a cron expression, with presets from every_minute through yearly. Choose which databases to include, then add an S3-compatible target. For Postgres, Coolify runs this:

pg_dump --format=custom --no-acl --no-owner --username postgres myapp

Restoring uses the matching tool:

pg_restore --verbose --clean -h localhost -U postgres -d postgres pg-dump-postgres-1700000000.dmp

One warning that bites people during upgrades: custom-format dumps are sensitive to version differences between pg_dump and pg_restore. Moving to a new Postgres major version goes more smoothly with a plain or tar dump, restored through psql. And test one restore into a scratch database now. A backup you have never restored is a guess, not a recovery plan.

Coolify's own database is backed up through the same mechanism, so a single bucket can hold both.

Step 8: upgrades

There are three modes. Automatic updates fetch new versions on their own. Semi-automatic shows an Upgrade button in the sidebar and waits for you to click it. Manual means running the installer again:

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

To pin a specific version, pass it through: bash -s 4.0.0-beta.400. The docs make one request before you touch any of this, and it is the same one that applies to every self-hosted platform: back up first. If you want to test a release before it reaches production, turn automatic updates off in Settings and upgrade the staging instance by hand.

When to use it, and when not to

Coolify fits when you run several applications and want a dashboard, TLS, database provisioning, and scheduled backups without writing that plumbing yourself.

Reach for plain Docker Compose plus a proxy when you have one or two services that rarely change. Fewer moving parts, and you keep full control, but certificates, backups, and the deploy script become your code.

Dokku gives you Heroku-style git push deployments from the CLI, with no dashboard. Kamal deploys from your CI straight to servers over SSH and leaves no long-running control plane on the box. Both are smaller commitments than a full PaaS, and both assume you are comfortable without a web UI.

A cloud PaaS, including Coolify's own hosted option, is the right answer when you do not want to be the operator at all. Coolify Cloud runs the control plane for you from 5 USD per month while your workloads stay on your servers.

Where self-hosting costs you, concretely:

  • Coolify manages servers over SSH, including the host it runs on. The docs are explicit that localhost is allowed but not recommended as your only target, because heavy server load can make Coolify itself unresponsive when you need it most.
  • One proxy container runs per server. Switching Traefik to Caddy, or to Custom with your own proxy, interrupts every public route until resources are redeployed with the new labels.
  • It is still one box. One VPS, one disk, one control plane. The S3 copy of your database and the .env file holding APP_KEY should genuinely live off the server.

Verdict

For a solo developer or a small team currently paying for several managed services, Coolify replaces a meaningful slice of that spend with a small VPS and an afternoon of setup. What you buy is convenience with an audit trail you control. What you take on is the operating system, the Docker daemon, the TLS renewals, and the restore drill.

Do the restore drill. That is the step that separates a backup configuration from a recovery capability.

Next steps

Set up instance backup so /data/coolify itself leaves the server, enable two-factor authentication on the admin account, and close port 8000 once the dashboard has a domain. If your team is bigger than you, add members with the right roles instead of sharing the root login.

Referensi

Need Help Implementing This?

I help teams design and build scalable cloud infrastructure, DevOps pipelines, and production-grade systems.

Book a Free Consultation