필사 모드: Choosing a Self-Hosted Deployment Platform in 2026 — The Five Axes That Separate Coolify, Dokploy, CapRover, Kamal, and Openship
English- Introduction — The Day Openship Landed, the Same Question Came Back
- The Five Axes That Decide It
- Where the Control Plane Lives
- What Rollback and Zero-Downtime Deploys Actually Look Like
- Secrets — Where Things Slip Most Often
- Comparing the Candidates
- You're Not Paying Money — You're Paying Attention
- Conclusion — Write Down Who Owns Maintenance First
- References
Introduction — The Day Openship Landed, the Same Question Came Back
Openship landed on GeekNews. The pitch is familiar yet ambitious — point it at a repository, and it detects the stack, builds the container, sets up the domain and TLS, and deploys. No config file, no pipeline, no YAML required. It's Apache 2.0 licensed and ships a desktop app, a web dashboard, a CLI, and a REST API.
And the comments, as always, converged on the same questions: "How is this different from Coolify?" "Why not just use compose?" "Who's going to maintain this?"
This post is a decision framework meant to answer that question. A feature-list comparison won't help — most of the candidates advertise the same things: git-push deploys, automatic TLS, databases, backups, logs. What actually decides whether you regret the choice three months later is five things that rarely make it onto that list. Up front: this post's conclusion is neither "self-hosting is better" nor "managed is better." Self-hosting is a trade where you pay attention instead of money, and the exchange rate is different for every team.
The Five Axes That Decide It
Instead of a feature matrix, filtering candidates through the following five questions usually narrows the field to two or fewer.
First, where does the control plane live? Is it a dashboard server that's always up, or a local CLI that only exists while a command is running? This determines what happens when the deployment system itself goes down.
Second, how many seconds and how many commands does a rollback take? At 3 a.m., is reverting a single UI button, manually hunting down the previous image tag and redeploying by hand, or not even documented?
Third, where and in what form are secrets stored? If they land in the platform's own database, that database's backups and access controls become the secret's actual security boundary.
Fourth, what changes when you go from one node to several? Most tools say "multi-server support," but there's a large gap in practice between deploying to several boxes individually and treating them as a cluster.
Fifth, how many hours a month does running this tool cost you? This is the number that actually compares against a managed PaaS bill.
Where the Control Plane Lives
On this axis, the candidates split cleanly into two camps.
The resident-control-plane camp — Coolify, Dokploy, CapRover, and Openship (server mode). A dashboard sits on the server, always running. In Coolify's case, the control plane handles the dashboard, an internal PostgreSQL, git webhook ingestion, and deployment orchestration, then reaches worker nodes over SSH to run containers. The common recommendation is that the control plane itself is fine on roughly 2 vCPU / 4GB.
# Install Coolify — make it a habit to read the script before running it
curl -fsSL https://cdn.coollabs.io/coolify/install.sh -o /tmp/coolify-install.sh
less /tmp/coolify-install.sh
bash /tmp/coolify-install.sh
This camp's advantage is clear: whoever deploys doesn't need to be a backend engineer. Viewing logs, editing environment variables, and redeploying all happen in a browser. One-click app templates can bring up Postgres, Redis, or a monitoring stack in minutes.
The downside is just as clear. You now have one more component that can fail independently — the control plane. If the dashboard goes down, apps that are already running keep running, but you can neither deploy nor roll back in that state. The dashboard is also an attack surface — after Coolify-related CVEs were reported in 2026, the recommendation to "pull the dashboard off the public internet and put it behind a VPN or an IP allowlist" kept coming up. Leaving a self-hosted deployment tool's dashboard open on 0.0.0.0 is effectively the same as leaving a root shell open.
The no-resident camp — Kamal 2, and plain compose + SSH. There is no control plane. It exists only while a command runs on a developer's laptop or a CI runner; once the command finishes, only containers and a proxy remain on the server.
# config/deploy.yml — Kamal 2
service: app
image: acme/app
servers:
web:
- 10.0.0.11
- 10.0.0.12
proxy:
ssl: true
host: app.example.com
healthcheck:
path: /up
interval: 3
registry:
server: ghcr.io
username: acme-ci
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
RAILS_ENV: production
secret:
- DATABASE_URL
- SECRET_KEY_BASE
accessories:
db:
image: postgres:17
host: 10.0.0.20
env:
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
kamal setup # first time only: installs Docker, starts the proxy, creates accessories
kamal deploy # build -> push -> rolling replace per server
kamal app logs -f # stream logs from every server
kamal rollback # revert to the last healthy version
kamal proxy reboot # restart only the proxy
Kamal 2 dropped Traefik in favor of its own kamal-proxy. Because it was built specifically for zero-downtime deploys, waiting for a new container to pass its health check before handing over traffic is the default behavior. It's also Rails 8's default deployment tool, which has made it a de facto standard in the Rails ecosystem, but the language doesn't matter as long as you can produce a Docker image.
This camp's advantage is that there's no control plane to fail. The downside is that there's no UI to check at 3 a.m. — viewing logs means using SSH or the CLI, and anyone on the team who isn't comfortable with a terminal is effectively excluded from the deploy flow.
Interestingly, Openship offers both modes: a desktop app that acts as a local control plane and deploys over SSH, and a separate mode where it's installed resident on the server to receive git-push deploys. That said, the public version as of this writing is in the 0.1.x range, and the repository itself describes it as "core is production-ready, actively developed." Multi-node clustering, private networking, and a visual CI/CD pipeline are all still listed as upcoming. It's worth trying on a new project, but it's premature as a target for migrating a service that's already running.
What Rollback and Zero-Downtime Deploys Actually Look Like
This is the item people check least when picking a deployment tool, and regret most often.
Virtually every candidate does zero-downtime deploys, and the mechanism is similar too — bring up a new container, and once it passes its health check, the proxy hands over traffic and the old container comes down. Kamal's kamal-proxy, Coolify and Dokploy's Traefik-based setups, CapRover's Docker Swarm rolling updates, and Openship's OpenResty-based edge all play that role.
The difference shows up in rollback. There are three things to check.
- Does the previous image stay on the server? If not, a rollback becomes "pull it again from the registry," and if the registry is unreachable, rollback becomes impossible outright. Kamal keeps the previous container around, so
kamal rollbackworks instantly. - Is the information a rollback needs visible right in the UI or CLI? If you have to dig through a registry console to find the previous deploy's image tag, you effectively have no rollback path.
- What happens to migrations? No tool solves this for you. Using backward/forward-compatible migrations and decoupling deploys from schema changes remains a design problem on the application side.
The third point is the crux. Even if a container rollback finishes in 30 seconds, if you can't revert the schema, your actual recovery time isn't 30 seconds. This is a decision that precedes tool choice.
One more thing. A rollback feature you've never actually practiced is as good as no rollback feature. You should deliberately ship a bad deploy to staging and practice reverting it at least once a quarter. The same goes for backup restores — the fact that backups are running and the fact that they can be restored are two separate claims.
Secrets — Where Things Slip Most Often
This is where the candidates' underlying character shows up most starkly.
Dashboard-style tools generally store secrets in their own database. You type them into the UI, and they're injected as environment variables at deploy time. Convenient, but the consequence is that database becomes your secret store. Three things to check: whether it's encrypted at rest, where the encryption key lives (if it's on the same server, the actual protection is limited), and whether secrets end up in dashboard backups as-is.
Kamal goes the opposite direction. It doesn't hold secrets itself; it reads them locally at deploy time and injects them into the container. The .kamal/secrets file is the conduit, and the recommended practice is to never commit that file and instead populate it from an external vault.
# .kamal/secrets — write "how to fetch it," not the value itself
KAMAL_REGISTRY_PASSWORD=$(op read "op://infra/ghcr/token")
DATABASE_URL=$(op read "op://infra/app/database-url")
SECRET_KEY_BASE=$(op read "op://infra/app/secret-key-base")
# in CI, the same slots get filled from CI secrets
# .kamal/secrets.production
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
DATABASE_URL=$DATABASE_URL
The advantage of this approach is that the single source of truth for secrets stays outside the deployment tool. Even if you swap out the deployment platform, your secret-management setup stays intact. The downside is one more layer to configure, and if the vault goes down, deploys are blocked.
Whichever side you choose, the minimum bar is the same: don't bake secrets into the image, keep them out of build logs, document your rotation procedure, and personally verify at least once whether secrets end up in plaintext inside the deployment tool's backups.
Comparing the Candidates
| Tool | Control Plane | Rollback | Where Secrets Live | Multi-Node | Status / License |
|---|---|---|---|---|---|
| Coolify | Resident dashboard + SSH workers | Pick a previous deploy in the UI | Platform DB | 1 control plane + N SSH workers | Mature, large community / Apache 2.0 |
| Dokploy | Resident dashboard (lightweight) | Pick a previous deploy in the UI | Platform DB | Docker Swarm-based | Active, lighter than Coolify / Apache 2.0 |
| CapRover | Resident dashboard | Redeploy the previous image tag | Platform config | Native Docker Swarm | Long-standing, stable / Apache 2.0 |
| Kamal 2 | None (only while the CLI runs) | kamal rollback, instant | Injected at deploy time from an external vault | Listed in the server list, rolling deploy | Mature, Rails 8 default / MIT |
| Openship | Choice of desktop, server, or cloud | Supported per the docs | Platform-managed | Upcoming | 0.1.x, early / Apache 2.0 |
| compose + runner | None (CI runs it) | You build it yourself | CI secrets or a vault | Configured separately per server | No tool, all DIY |
The table boils down to one line: if anyone on the team isn't comfortable with a terminal, go dashboard-style; if everyone is a backend engineer, go Kamal or compose. This one criterion accounts for most of the real-world satisfaction difference.
Plain compose is also a serious option: put docker compose and a systemd unit on the server, and have a GitHub Actions self-hosted runner or a webhook receiver run git pull && docker compose up -d. If you have one or two services and deploy weekly, this setup goes the longest on the fewest concepts. But you have to build zero-downtime deploys and rollback yourself, and if you skip that part, every deploy causes a few seconds of downtime. Whether you can tolerate those few seconds is the deciding factor.
You're Not Paying Money — You're Paying Attention
The most common distortion in self-hosting discussions is comparing cost by price alone. Weigh a managed PaaS at $200 a month against a VPS at $40 a month, and the conclusion looks obvious. But that $200 bill also covers work we wouldn't otherwise be doing ourselves.
Move to self-hosting, and the following becomes your job.
- OS security patches and reboot scheduling
- Upgrading the Docker engine and the deployment platform itself (and recovering when an upgrade breaks)
- Disk usage management — old images and build caches filling the disk and stalling deploys is genuinely common
- Backup schedules and restore drills
- Dashboard access control, keeping a VPN or an allowlist in place
- Handling certificate renewal failures (even automated, they still fail sometimes)
- And on-call for all of the above
Converted into hours, it varies by team, but for a handful of services, 2-4 hours a month in a normal state and a whole day in a month when something breaks is a common range. Depending on what you value an engineer-hour at, this math flips easily.
So here's the honest conclusion.
- Self-hosting wins when: someone on the team already knows infrastructure, you need servers anyway (background workers, always-on processes, GPUs), you have data-residency requirements, or your managed bill has bloated relative to your traffic.
- Managed wins when: the team is small and needs to focus on the product, traffic is unpredictable, there's no one to put on call, deploys are frequent, and there are no compliance requirements.
- The worst case: you move to self-hosting and no one actually owns the upkeep. This ends up more expensive than managed ever was — six months later, production is running on a platform nobody can upgrade anymore.
For the practical issues of migrating the container runtime itself, see the Docker to Podman migration piece.
Conclusion — Write Down Who Owns Maintenance First
New tools like Openship keep appearing because this problem still isn't well solved. Wanting both the convenience of a managed PaaS and control over your own servers is a legitimate ask, and Coolify, Dokploy, and Kamal each answer it from a different angle.
- Filter candidates through the five axes — control plane location, rollback path, where secrets live, multi-node behavior, monthly operational hours. The feature matrix comes after that.
- If you go with a dashboard-style tool, pull the dashboard off the public internet. Behind a VPN or an IP allowlist should be the default.
- If you go with Kamal or compose, document rollback and log access. With no UI, procedure becomes institutional knowledge.
- Openship is still 0.1.x. Try it on a new side project first, and hold off on workloads that need multi-node until the roadmap actually lands.
- On the first line of your adoption doc, write down the name of the person who owns upgrading this platform. If that name is blank, comparing tools is pointless.
Self-hosting doesn't eliminate the cost. It just moves it from the invoice to the calendar.
References
- Openship — self-hosted deployment platform repository (Apache 2.0)
- Coolify — official site and docs
- Dokploy — official site
- CapRover — official site
- Kamal — official docs and the 2.x upgrade guide
- basecamp/kamal-proxy — Kamal 2's own proxy
- Docker Compose file reference
- Docker to Podman migration (related post)
현재 단락 (1/115)
[Openship landed on GeekNews](https://github.com/oblien/openship). The pitch is familiar yet ambitio...