<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: huangchengsir</title>
    <description>The latest articles on DEV Community by huangchengsir (@huangchengsir).</description>
    <link>https://dev.to/huangchengsir</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3987670%2Fa22d0c53-2e74-433b-9def-c3fb51c0f353.png</url>
      <title>DEV Community: huangchengsir</title>
      <link>https://dev.to/huangchengsir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/huangchengsir"/>
    <language>en</language>
    <item>
      <title>You probably don't need ArgoCD - good-enough GitOps with git and docker compose</title>
      <dc:creator>huangchengsir</dc:creator>
      <pubDate>Thu, 18 Jun 2026 13:31:20 +0000</pubDate>
      <link>https://dev.to/huangchengsir/you-probably-dont-need-argocd-good-enough-gitops-with-git-and-docker-compose-4jk3</link>
      <guid>https://dev.to/huangchengsir/you-probably-dont-need-argocd-good-enough-gitops-with-git-and-docker-compose-4jk3</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Let me say the quiet part out loud: &lt;strong&gt;for a single host, or three-to-five machines, you do not need ArgoCD.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "good-enough GitOps" actually is
&lt;/h2&gt;

&lt;p&gt;GitOps boils down to two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Declarative&lt;/strong&gt;: the desired state lives in git (for self-hosting, that's your &lt;code&gt;docker-compose.yml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull-based&lt;/strong&gt;: the machine pulls changes and reconciles itself, instead of you SSHing in to type commands.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On one docker host, the minimal version looks like this: a webhook (or a 60-second &lt;code&gt;git fetch&lt;/code&gt;) notices the tracked branch moved, and runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull &lt;span class="nt"&gt;--ff-only&lt;/span&gt;
docker compose pull
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--remove-orphans&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--remove-orphans&lt;/code&gt; 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 -&amp;gt; push -&amp;gt; the machine reconciles itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two things that will actually bite you
&lt;/h2&gt;

&lt;p&gt;The good-enough version runs, but there are two traps anyone who's done this knows:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Zero-downtime
&lt;/h3&gt;

&lt;p&gt;Plain &lt;code&gt;docker compose up -d&lt;/code&gt; &lt;strong&gt;recreates&lt;/strong&gt; 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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;compose up --wait&lt;/code&gt; (v2.17+) at least &lt;strong&gt;waits for the healthcheck&lt;/strong&gt; before calling the deploy a success, which catches the "starts then crashes" case;&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't expect compose to do graceful rolling updates for you. It doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rollback
&lt;/h3&gt;

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

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

&lt;h2&gt;
  
  
  When you should reach for a heavier tool
&lt;/h2&gt;

&lt;p&gt;The boundary is clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One machine&lt;/strong&gt;: the twenty-line script is genuinely fine. Don't overthink it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3+ machines, or you need an audit trail of who deployed what, when&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  I eventually packaged this up
&lt;/h2&gt;

&lt;p&gt;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 &lt;strong&gt;Pipewright&lt;/strong&gt;: 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 &lt;code&gt;embed.FS&lt;/code&gt;, SQLite by default).&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/huangchengsir/pipewright" rel="noopener noreferrer"&gt;https://github.com/huangchengsir/pipewright&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Issues and pushback welcome.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>go</category>
      <category>selfhosted</category>
      <category>docker</category>
    </item>
    <item>
      <title>I built a self-hosted CI/CD + deploy + ops platform that fits in one Go binary</title>
      <dc:creator>huangchengsir</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:51:21 +0000</pubDate>
      <link>https://dev.to/huangchengsir/i-built-a-self-hosted-cicd-deploy-ops-platform-that-fits-in-one-go-binary-5g8</link>
      <guid>https://dev.to/huangchengsir/i-built-a-self-hosted-cicd-deploy-ops-platform-that-fits-in-one-go-binary-5g8</guid>
      <description>&lt;p&gt;I run a handful of small VPS boxes for side projects. For a while my "deploy setup" was the usual trio: a CI runner (Jenkins, then Drone), something to push releases to servers (Ansible / Kamal scripts), and Portainer to actually &lt;em&gt;see&lt;/em&gt; what was running and poke at containers.&lt;/p&gt;

&lt;p&gt;On a 2GB box, wiring and babysitting three separate tools was more overhead than the apps I was shipping. So a few weeks ago I started building the thing I actually wanted: &lt;strong&gt;CI/CD + multi-server deployment + basic ops, in a single Go binary with zero runtime dependencies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's called &lt;strong&gt;Pipewright&lt;/strong&gt;. You scp one file to a server, run it, open the browser — that's the whole install.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual pipeline builder&lt;/strong&gt; — a two-level (stage → job) DAG canvas. Horizontal links run serially, vertical ones run in true parallel. It round-trips with &lt;code&gt;.pipewright.yml&lt;/code&gt;, so you can click &lt;em&gt;or&lt;/em&gt; commit your pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolated builds&lt;/strong&gt; — version-pinned, container-isolated build steps with dependency caching; artifacts (images / JARs / dist bundles) you can push to a private registry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentless deploys over SSH&lt;/strong&gt; — zero-downtime switchover + rollback on failure, fan-out to multiple servers with partial-failure visibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server &amp;amp; container ops&lt;/strong&gt; — manage containers / images / stacks / volumes / networks across hosts, live stats, logs, and an in-browser terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can update itself&lt;/strong&gt; — checks GitHub for the latest release and does a download → checksum verify → atomic replace → restart, from the UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc54p20ufl8b9tzum1nqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc54p20ufl8b9tzum1nqv.png" alt="Visual pipeline canvas" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Go backend, Vue 3 frontend, everything embedded into the one binary. SQLite by default (zero setup), MySQL if you want it. Auth is argon2id + CSRF, credentials live in an encrypted vault (never echoed back in plaintext), and there's an append-only audit log. ~50MB binary, runs comfortably on a tiny box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkb27mw4kbpmzp160dm6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkb27mw4kbpmzp160dm6m.png" alt="Run detail with live logs" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned building it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Embedding the whole frontend into the Go binary&lt;/strong&gt; (&lt;code&gt;embed.FS&lt;/code&gt;) is underrated. One artifact, no nginx, no separate static host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-update is a great forcing function.&lt;/strong&gt; The moment your tool updates itself in production, you stop being sloppy about release artifacts, checksums, and atomic file replacement. I've been dogfooding Pipewright to ship its own releases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"One binary for all of it" is a real design tension.&lt;/strong&gt; It's tempting to keep adding surface area. I keep reminding myself the goal is the &lt;em&gt;opposite&lt;/em&gt; of Jenkins-at-scale: the lightest thing that still does the full build → deploy → watch loop for a few servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it's NOT
&lt;/h2&gt;

&lt;p&gt;It's not trying to replace GitLab/Jenkins for a 200-engineer org. No multi-tenant RBAC, no massive plugin ecosystem. If you're running a big org, use the big tools. If you're a solo dev or small team who just wants build → deploy → ops without standing up three services, that's exactly who I built it for.&lt;/p&gt;

&lt;p&gt;It's early (a few weeks old) and I'm actively building. I'd genuinely love feedback — especially on whether the "all-in-one binary" framing makes sense to you, or whether it's doing too much.&lt;/p&gt;

&lt;p&gt;Repo (MIT, screenshots + one-command quickstart): &lt;strong&gt;&lt;a href="https://github.com/huangchengsir/pipewright" rel="noopener noreferrer"&gt;https://github.com/huangchengsir/pipewright&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading 🙏&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>devops</category>
      <category>go</category>
      <category>selfhosted</category>
    </item>
  </channel>
</rss>
