Coolify spent two years in beta and went stable with v4.0.0 in April 2026. Five months later, its click-ops panel gained something that marks every maturing platform: a Terraform provider. The community-built bindtech-xyz/coolify provider turns projects, environments, applications, databases, and even server settings into HCL you can version, review, and plan before you apply.
This is panel-as-code arriving for the most popular single-box PaaS in self-hosting. What exactly it covers, what a working setup looks like, and where it stops — that's this post.
What you can manage as code
The provider is built on Terraform's plugin framework speaking protocol v6, which means one binary serves both Terraform (1.1 and up) and OpenTofu (1.6 and up) — the two engines share the same provider wire protocol, so no fork or shim is needed. It is written against Coolify's main-branch controllers (v4.3.x), deliberately not against the lagging OpenAPI spec.
The framework choice matters for longevity. Providers built on the legacy SDK pin themselves to protocol v5 and HashiCorp's older plugin model; a plugin-framework provider on protocol v6 is the path both engines committed to — OpenTofu implements protocols v5 and v6 identically, state files are interchangeable, and modules resolve from either registry. For a community provider maintained outside any vendor, betting on the shared protocol instead of one engine's SDK is what keeps both terraform and tofu working without the maintainer testing two worlds.
The initial release covered 12 resources and 10 data sources. It has since grown to 30 resources and 22 data sources, tracking the Coolify docs sidebar end to end. Grouped by what an operator actually does:
Servers and identity. coolify_private_key and coolify_server register the SSH keys and machines Coolify deploys to; coolify_destination models Docker networks; coolify_server_settings manages proxy choice, Docker cleanup policies, Sentinel, Cloudflare Tunnels, and log drains. Data sources read servers, domains, and per-server resources back.
Projects and environments. coolify_project and coolify_environment model the hierarchy every workload hangs from, with coolify_tag plus coolify_resource_tag for team-wide labeling.
Workloads. coolify_application deploys in five modes — public git, private git via deploy key, private git via GitHub App, inline Dockerfile, or registry image — with build packs spanning nixpacks, static, dockerfile, dockercompose, and railpack. coolify_database provisions eight engines (PostgreSQL, MySQL, MariaDB, MongoDB, Redis, KeyDB, Dragonfly, ClickHouse). coolify_service covers both one-click services and raw docker-compose files, and a coolify_service_templates data source reads the 300-plus-template catalog from the live CDN feed.
Configuration and operations. Environment variables come in unitary, bulk, and shared (team/project/environment/server-scoped) flavors. Persistent storage, volume backups, database backups with S3 targets and retention, scheduled cron tasks, and notification settings (email, Discord, Slack, Telegram, Pushover, webhook) all have resources. coolify_deployment triggers deploys by UUID or tag with an optional wait-for-completion, coolify_resource_action declares start/stop/restart, and coolify_cloud_server provisions fresh VPS capacity on Hetzner, DigitalOcean, or Vultr.
Two mechanical details make this genuinely usable rather than demo-ware. Deletes of applications, databases, services, and destinations poll until Coolify's asynchronous teardown actually finishes, so destroy-then-recreate cycles never collide on names, domains, or networks. And an import guide covers adopting click-built panel state into HCL, so long-running instances don't have to start over.
The read side deserves its own note because it completes the ops loop. The 22 data sources answer the questions automation actually asks: coolify_deployments and coolify_backup_executions report what ran; coolify_instance reports panel health and version; coolify_cloud_catalog lists the regions, sizes, and images behind cloud provisioning; coolify_server_domains and coolify_server_resources expose what each box serves and holds; the tags and teams data sources let policy tooling inventory who owns what. Resources declare intent; these data sources observe reality. A backup-audit script or a deploy-status dashboard can read them without touching the panel at all.
A minimal working setup
A project, an environment, and a git-backed app takes one file. The provider needs an endpoint and a token — minted under Keys and Tokens in the panel, passed via the COOLIFY_TOKEN environment variable so it never lands in git:
terraform {
required_providers {
coolify = {
source = "bindtech-xyz/coolify"
version = "~> 0.1"
}
}
}
provider "coolify" {
endpoint = "https://coolify.example.com"
}
resource "coolify_project" "shop" {
name = "shop"
description = "Managed by Terraform"
}
resource "coolify_environment" "staging" {
project_uuid = coolify_project.shop.uuid
name = "staging"
}
data "coolify_servers" "main" {}
resource "coolify_application" "web" {
project_uuid = coolify_project.shop.uuid
environment_name = coolify_environment.staging.name
server_uuid = data.coolify_servers.main.servers[0].uuid
git_repository = "https://github.com/example/shop"
git_branch = "main"
build_pack = "nixpacks"
ports_exposes = "3000"
domains = "https://staging.example.com"
instant_deploy = true
}One gotcha, straight from the docs: Coolify creates a default production environment with every project. If you want production under HCL management, import it (terraform import coolify_environment.production <project-uuid>/production) instead of declaring a fresh one — the example above sidesteps this by managing staging. A second quirk worth knowing: Coolify rejects description punctuation outside a small allowlist with a 422, so keep descriptions to letters, numbers, and tame punctuation, or applies will fail in confusing ways.
What panel-as-code actually buys you
Three concrete wins, in increasing order of value.
Versioned environment definitions. Every app, env var, backup schedule, and notification channel becomes a reviewed diff instead of a remembered click. When staging works and production doesn't, git log on the HCL answers "what changed" faster than any panel archaeology.
Repeatable project scaffolding. A new client, a new side project, a staging clone of production — each becomes a module instantiation instead of an afternoon of clicking through the same five screens. The bulk env-var resource and the shared-variables scoping (team, project, environment, server) are what make this scale past one app.
Drift detection on the panel's own state. Somebody flips a setting in the dashboard at 11 p.m. to fix an incident. The next plan shows the diff, and the team decides deliberately whether to keep it (update the HCL) or revert it — instead of discovering the toggle six months later during an unrelated outage.
The provider's own design choices reinforce all three: PR-preview resources clean themselves up on destroy, and the async-delete polling means the plan/apply loop behaves deterministically even though Coolify's API is eventually consistent underneath.
A fourth win follows from the first three: plan output belongs in CI. Point a pull-request check at terraform plan (or tofu plan) against the Coolify instance and every environment change gets a diff, a reviewer, and a merge commit before it touches the panel — the same workflow teams already use for cloud infrastructure, now extended to the PaaS layer. The provider authenticates with a single API token, so the CI job needs one secret and read access to the HCL repo. Click-ops can't be reviewed; HCL can.
Where it stops: the machine lifecycle underneath
Here is the boundary, and it matters more than the feature list. The provider manages what Coolify models — and Coolify models Docker workloads on servers you registered, not the servers themselves.
Registering a server still means bringing a machine: an IP address, an SSH key, a Docker daemon reachable over the network. The coolify_cloud_server resource narrows this by creating VPS instances on Hetzner, DigitalOcean, or Vultr, but creating a VM is not managing a fleet. Nothing in the loop reconciles desired against actual machine state, replaces a failed node, cordons a sick host, or rolls an OS upgrade across servers one at a time. If a box dies, the panel shows a dead server and HCL has no opinion about what happens next. Coolify v5's multi-server story is actively being built, but v4 is a single-server-or-handful-of-servers world, and the provider faithfully encodes that world.
This is the exact seam Cluster API was built for: declarative machine lifecycle — MachineDeployments, health checks that replace failed nodes, rolling upgrades of the fleet itself — as reconciled API objects rather than registration records. A panel provider cannot grow that from underneath; it can only manage the layer its panel understands. HCL-over-panel gives you git history and repeatability for everything above the machine line. Below the line, you still own the machines the old-fashioned way.
When HCL-over-panel is enough
The decision is simpler than the feature list suggests:
- Enough: you run Coolify on one box or a few long-lived servers, and your pain is click-ops sprawl — untracked changes, painful re-setup, no review trail. Adopt the provider, import existing state, and never hand-configure an app again.
- Enough with discipline: you use
coolify_cloud_serverto stamp out capacity, but you treat those VMs as pets with birth certificates, not cattle — you still track their health and lifecycle outside HCL. - Papering over: you need nodes to come and go on their own — autoscaling, automatic failed-node replacement, rolling fleet upgrades. No provider can reconcile what its API doesn't model. That's the layer a Cluster-API-based platform owns: bex manages the machine lifecycle declaratively, so the fleet converges on the declared state instead of merely remembering what you clicked.
The provider's arrival is still a genuine milestone. Coolify graduated from beta to stable in April; five months later its entire control surface is committable. For the single-box operators who made Coolify the default self-hosted PaaS, the dashboard was the product — and now the dashboard has a git history.



