DEV Community

huangchengsir
huangchengsir

Posted on

You probably don't need ArgoCD - good-enough GitOps with git and docker compose

Every time someone starts self-hosting - a homelab, a few internal services for a small team - they tend to fall down the same rabbit hole: should I run K8s? And if I run K8s, do I need ArgoCD or Flux for GitOps? Two weeks later they've read a pile of Helm charts and CRDs and still haven't deployed a single service.

Let me say the quiet part out loud: for a single host, or three-to-five machines, you do not need ArgoCD. The part of GitOps you actually want - "the git repo is the source of truth, changes deploy themselves, I can roll back, and there's a record" - you can get 90% of it with git + docker compose and about twenty lines of script, with no control plane to babysit.

What "good-enough GitOps" actually is

GitOps boils down to two things:

  1. Declarative: the desired state lives in git (for self-hosting, that's your docker-compose.yml).
  2. Pull-based: the machine pulls changes and reconciles itself, instead of you SSHing in to type commands.

On one docker host, the minimal version looks like this: a webhook (or a 60-second git fetch) notices the tracked branch moved, and runs:

git pull --ff-only
docker compose pull
docker compose up -d --remove-orphans
Enter fullscreen mode Exit fullscreen mode

--remove-orphans matters: services you delete from the compose file actually get stopped, instead of lingering forever. That's it - you now have a working GitOps loop: edit compose -> push -> the machine reconciles itself.

The two things that will actually bite you

The good-enough version runs, but there are two traps anyone who's done this knows:

1. Zero-downtime

Plain docker compose up -d recreates the container - old one stops, new one starts, with a few seconds of gap in between. Fine for background jobs, but for anything user-facing those few seconds are a 502.

  • compose up --wait (v2.17+) at least waits for the healthcheck before calling the deploy a success, which catches the "starts then crashes" case;
  • for true zero-downtime you end up running two service names (blue/green) behind a reverse proxy, flipping traffic once the new one is healthy, then stopping the old.

Don't expect compose to do graceful rolling updates for you. It doesn't.

2. Rollback

This is the one most people skip and the one that hurts most. If your image tag is :latest, then "rollback" means "edit the file and pray" - you have no idea which version last worked.

The fix: pin image tags to the git commit SHA (myapp:9f8322f, not myapp:latest), and record which SHA is currently live. Now rollback degrades to "re-deploy the previous SHA" - deterministic and repeatable. Without it, your GitOps quietly turns into "git pull and hope".

When you should reach for a heavier tool

The boundary is clear:

  • One machine: the twenty-line script is genuinely fine. Don't overthink it.
  • 3+ machines, or you need an audit trail of who deployed what, when: this is where hand-rolled scripts start accruing debt - you need concurrent multi-host deploys, unified rollback, a record of who triggered it. That's when a thicker tool starts paying for itself.

But note: that threshold is much later than most people assume. For the vast majority of self-hosting, you're still on the "twenty-line script is fine" side.

I eventually packaged this up

I understood all of the above, and still got tired of re-typing the webhook + pull + compose + SHA-pinning + rollback-record dance every time I set up a new machine. So I bundled it - along with the CI build step before it, agentless multi-host SSH deploys, and container management - into a single Go binary called Pipewright: scp one file to a server, run it, open the browser, and you get a visual pipeline + one-click deploy + a container panel, with no other runtime dependencies (the Vue frontend is compiled into the binary via embed.FS, SQLite by default).

It's basically the "good-enough GitOps" above, productized, with the zero-downtime and SHA-based rollback gaps filled in. MIT licensed, aimed at solo devs and small teams - not trying to replace GitLab for a 200-engineer org.

Repo: https://github.com/huangchengsir/pipewright

But even if you never touch it, the takeaway stands: for self-hosted GitOps, you probably don't need ArgoCD. Start with git + compose, and only add weight when you actually hit the wall.

Issues and pushback welcome.

Top comments (0)