Infrastructure

Deploying with Coolify on your own server, without lock-in

How we deploy this site on a VPS with Coolify, Docker and Cloudflare: CI decides what reaches production and nothing depends on a single vendor.

4 min read

Managed hosting platforms are convenient until they change the price, the rules or the limit you care about. For this site we wanted the opposite: a server of our own, built from parts that can be swapped one at a time. Here is how it’s put together and why.

The parts

Part What it does If it has to change tomorrow
VPS Runs the containers Any provider with Docker
Coolify Builds and deploys the containers, with its Traefik proxy It’s open source; the site doesn’t need it to run
Docker Packages the site: the same image locally, in CI and in production Standard
Cloudflare DNS, protection and caching in front of the server The site works without it; you lose the cache
GitHub Actions Tests every change and decides whether it ships The workflow is a file in the repository

The underlying rule: everything that defines the site lives in the repository. The Dockerfile, the server and its headers, the tests and the deployment workflow. If the server disappears, the site is rebuilt on another one.

A server just for this

The site runs on a dedicated VPS, separate from any personal or experimental project. The reason isn’t capacity, it’s the blast radius: a site that prides itself on security shouldn’t share a server with experiments. If one of those is compromised, it doesn’t take the business with it.

The server only accepts web traffic that comes through Cloudflare, over IPv4 and IPv6, and we verify that from the outside, not by reading the configuration. Coolify’s dashboard isn’t exposed to the internet.

The image

The site is built in a multi-stage Docker image: the first stage installs dependencies and generates the site; the final one keeps only what’s needed to serve it, with an unprivileged user and a health check that Coolify polls. With docker compose up we run locally exactly what runs in production, plus a test mailbox for the contact form.

CI decides what ships

The repository is private and the account is free, so GitHub doesn’t allow protecting the main branch. With Coolify’s automatic deploys, a push with failing tests would reach production. We solved it like this:

  1. In Coolify, automatic deployment is off.
  2. When a change is merged, CI runs lint, types, build, unit tests, browser tests with axe, and Lighthouse.
  3. Only if everything passes, a second workflow calls Coolify’s API to deploy that exact commit and, when done, purges Cloudflare’s cache.

If CI fails, nothing ships, and the failure is visible. Paid branch protection would have solved the same thing; this costs nothing and uses something we already had.

Caching does the heavy lifting

The server is far from most of our visitors, and that distance shows on every request that reaches it. Cloudflare doesn’t cache HTML by default, so we added an explicit rule to do it and purge the cache on every deploy. That way, almost every visit is served from the nearest Cloudflare node. You can see it in Lighthouse: mobile LCP on the home page is around 1.5 seconds.

Backups away from the provider

Databases (today, the visit counter’s) are backed up daily to S3-compatible storage at a different provider. A backup on the same server is useless if that server disappears. And a backup that has never been restored isn’t really a backup: testing a restore is on our pre-launch checklist.

What it costs

None of this is free in attention. A server of your own needs updates, monitoring and, now and then, reading a log at an odd hour. In exchange, you know exactly what runs, where and why, and you can move it whenever you want. For a studio that sells custom software, that transparency is part of the product too.

Want something like this for your application? Tell us about your project.

Have a process that could work better?

Tell us how you work today. That is enough for a first conversation.

Tell us about your project
Tell us about your project