<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Deployment on</title><link>/tags/deployment/</link><description>Recent content in Deployment on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sun, 15 Jun 2025 00:00:00 +0000</lastBuildDate><atom:link href="/tags/deployment/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 8: Zero-Downtime Deploys — Rolling updates without dropping requests</title><link>/post/go/go-deploy-zero-downtime/</link><pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-zero-downtime/</guid><description>&lt;p&gt;The first rolling deployment I did without graceful shutdown handling produced about 200 errors for in-flight requests. Kubernetes sent &lt;code&gt;SIGTERM&lt;/code&gt; to the old pods, the Go process exited immediately, and every active request was terminated mid-flight. The users got 502s. The monitoring dashboard lit up red for about 30 seconds per deploy, every time.&lt;/p&gt;
&lt;p&gt;Kubernetes&amp;rsquo;s rolling update strategy replaces pods one at a time and can achieve zero request drops — but only if your application cooperates. The contract is: Kubernetes sends &lt;code&gt;SIGTERM&lt;/code&gt;, your application finishes in-flight requests, then exits. If you ignore &lt;code&gt;SIGTERM&lt;/code&gt; and exit immediately, Kubernetes kills you with &lt;code&gt;SIGKILL&lt;/code&gt; after the grace period anyway, and you drop the requests. The work is in your application code, not in the Kubernetes config.&lt;/p&gt;</description></item><item><title>Lesson 8: Release Profiles and Build Optimization — Shipping fast binaries</title><link>/post/rust/rust-deploy-release-profiles/</link><pubDate>Thu, 08 May 2025 13:50:00 +0000</pubDate><guid>/post/rust/rust-deploy-release-profiles/</guid><description>&lt;p&gt;I was benchmarking two builds of the same service — one with default release settings, one with a tuned profile. Same code. Same hardware. The tuned build was 22% faster on our hot path and 40% smaller. I didn&amp;rsquo;t change a single line of Rust. Just &lt;code&gt;Cargo.toml&lt;/code&gt; settings.&lt;/p&gt;
&lt;p&gt;Most Rust developers know about &lt;code&gt;cargo build --release&lt;/code&gt;. Fewer know that &lt;code&gt;--release&lt;/code&gt; is just a starting point — there&amp;rsquo;s a whole set of knobs in the release profile that trade compile time for runtime performance, or binary size, or debuggability. Let me walk you through every one that matters.&lt;/p&gt;</description></item><item><title>Lesson 7: Configuration — Environment, files, feature flags</title><link>/post/rust/rust-deploy-config/</link><pubDate>Mon, 05 May 2025 10:15:00 +0000</pubDate><guid>/post/rust/rust-deploy-config/</guid><description>&lt;p&gt;I once shipped a service to production with the staging database URL hardcoded. Not in an environment variable — literally in the source code, in a &lt;code&gt;const&lt;/code&gt;. It ran for two hours writing production data to the staging database before anyone noticed. The fix was easy. The data migration to clean up the mess took three days.&lt;/p&gt;
&lt;p&gt;Configuration is one of those things that seems trivial until it bites you. And in Rust, we have the type system to make configuration bulletproof — but only if we structure things right. Let me walk you through the approach I&amp;rsquo;ve converged on after making every possible configuration mistake.&lt;/p&gt;</description></item><item><title>Lesson 6: Graceful Shutdown — Draining connections cleanly</title><link>/post/rust/rust-deploy-graceful-shutdown/</link><pubDate>Fri, 02 May 2025 19:45:00 +0000</pubDate><guid>/post/rust/rust-deploy-graceful-shutdown/</guid><description>&lt;p&gt;I deployed a new version of a payment service once, and for about 3 seconds during the rollout, a handful of transactions just&amp;hellip; vanished. They weren&amp;rsquo;t in the database. They weren&amp;rsquo;t in the error logs. The old pods received the requests, started processing them, and then Kubernetes killed the pods before they finished. SIGKILL doesn&amp;rsquo;t ask nicely — it just terminates the process. Those transactions were gone.&lt;/p&gt;
&lt;p&gt;Graceful shutdown is the solution. When your service receives SIGTERM (Kubernetes&amp;rsquo;s way of saying &amp;ldquo;please stop&amp;rdquo;), it should stop accepting new requests, finish processing in-flight requests, flush any buffered data, close connections cleanly, and &lt;em&gt;then&lt;/em&gt; exit. Get this right and you get zero-downtime deployments. Get it wrong and you get data loss.&lt;/p&gt;</description></item><item><title>Lesson 5: Health Checks and Readiness Probes — Production liveness</title><link>/post/rust/rust-deploy-health-checks/</link><pubDate>Wed, 30 Apr 2025 14:05:00 +0000</pubDate><guid>/post/rust/rust-deploy-health-checks/</guid><description>&lt;p&gt;A service I maintained once passed all its health checks while silently dropping 30% of incoming requests. The health endpoint returned 200 OK every time Kubernetes asked. The database connection pool was exhausted, the service couldn&amp;rsquo;t process anything, but that little &lt;code&gt;/health&lt;/code&gt; endpoint — which didn&amp;rsquo;t touch the database — happily reported everything was fine.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s when I learned the difference between a health check that checks health and one that just says &amp;ldquo;the process is running.&amp;rdquo; They&amp;rsquo;re very different things, and getting this wrong means your orchestrator keeps sending traffic to a broken instance instead of replacing it.&lt;/p&gt;</description></item><item><title>Lesson 4: Observability — tracing, metrics, OpenTelemetry</title><link>/post/rust/rust-deploy-observability/</link><pubDate>Sun, 27 Apr 2025 08:22:00 +0000</pubDate><guid>/post/rust/rust-deploy-observability/</guid><description>&lt;p&gt;I once spent six hours debugging a production issue where requests were randomly timing out. No errors in the logs. CPU and memory looked fine. Response times were normal — except for the 2% that took 30 seconds. Without distributed tracing, I was blind. I ended up bisecting the problem by adding &lt;code&gt;println!&lt;/code&gt; statements, deploying them one at a time, and watching CloudWatch. It was miserable.&lt;/p&gt;
&lt;p&gt;That experience permanently changed how I build services. Observability isn&amp;rsquo;t something you bolt on when things break. It&amp;rsquo;s something you build in from day one, or you pay for it later — with your time, your sleep, and your sanity.&lt;/p&gt;</description></item><item><title>Lesson 3: CI/CD for Rust — GitHub Actions, caching, cargo-nextest</title><link>/post/rust/rust-deploy-ci/</link><pubDate>Thu, 24 Apr 2025 11:30:00 +0000</pubDate><guid>/post/rust/rust-deploy-ci/</guid><description>&lt;p&gt;My first Rust CI pipeline took 45 minutes. Forty-five. Every push triggered a full dependency build, tests ran sequentially, and clippy ran as a separate job that also built everything from scratch. I was burning through GitHub Actions minutes like they were free — which they are for open source, but my patience certainly wasn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Getting Rust CI right is mostly about caching. The compilation model means a clean build downloads and compiles hundreds of crates, and without caching, every single CI run pays that cost. Let me show you the pipeline I&amp;rsquo;ve landed on after iterating through dozens of projects.&lt;/p&gt;</description></item><item><title>Lesson 2: Static Linking with musl — Single binary deploys</title><link>/post/rust/rust-deploy-static-linking/</link><pubDate>Tue, 22 Apr 2025 16:40:00 +0000</pubDate><guid>/post/rust/rust-deploy-static-linking/</guid><description>&lt;p&gt;Last year I had to deploy a Rust service to a hardened environment — no package manager, no shared libraries, no internet access. Just a bare Linux kernel and my binary. If my binary depended on libc, libssl, or anything else in &lt;code&gt;/usr/lib&lt;/code&gt;, it simply wouldn&amp;rsquo;t start. That constraint forced me to learn static linking properly, and it turned out to be one of the best deployment patterns I&amp;rsquo;ve ever used.&lt;/p&gt;</description></item><item><title>Lesson 1: Docker for Rust — Multi-stage builds, minimal images</title><link>/post/rust/rust-deploy-docker/</link><pubDate>Sun, 20 Apr 2025 09:15:00 +0000</pubDate><guid>/post/rust/rust-deploy-docker/</guid><description>&lt;p&gt;The first time I shipped a Rust service in Docker, my image was 2.1 GB. Two. Point. One. Gigabytes. For a binary that was 8 MB. I&amp;rsquo;d used &lt;code&gt;rust:latest&lt;/code&gt; as my base, ran &lt;code&gt;cargo build --release&lt;/code&gt; inside it, and called it a day. The image had GCC, LLVM, every system library known to humanity, and my tiny HTTP server somewhere in the corner.&lt;/p&gt;
&lt;p&gt;That was the day I learned about multi-stage builds. And honestly, getting Docker right for Rust is one of those things that separates &amp;ldquo;I deployed it&amp;rdquo; from &amp;ldquo;I deployed it well.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 7: Profiling in Containers — pprof works in Kubernetes too</title><link>/post/go/go-deploy-profiling-containers/</link><pubDate>Sat, 12 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-profiling-containers/</guid><description>&lt;p&gt;The first time I needed to profile a Go service in production, I assumed I&amp;rsquo;d have to deploy a special build, reproduce the problem locally, or use some heavyweight APM product. Then I learned that Go&amp;rsquo;s &lt;code&gt;net/http/pprof&lt;/code&gt; package can serve live profiling data from a running process via HTTP — in a container, in Kubernetes, right now, without redeployment.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pprof&lt;/code&gt; is the built-in Go profiler. It can capture CPU profiles (what functions are consuming CPU time), heap profiles (what&amp;rsquo;s allocated in memory and by whom), goroutine profiles (how many goroutines exist and what they&amp;rsquo;re doing), and block/mutex profiles (where goroutines are waiting). All of this is available as HTTP endpoints that you can scrape with &lt;code&gt;go tool pprof&lt;/code&gt; from your laptop.&lt;/p&gt;</description></item><item><title>Lesson 6: Race Detector in CI — Run -race on every PR or ship bugs</title><link>/post/go/go-deploy-race-ci/</link><pubDate>Wed, 12 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-race-ci/</guid><description>&lt;p&gt;A data race is a memory safety bug. Two goroutines access the same variable without synchronization, at least one is writing, and the Go memory model makes no guarantees about what happens. In practice: values get corrupted, counters drift, maps panic at runtime. These bugs are intermittent — they appear under load, on fast machines, during deploys — and they&amp;rsquo;re nearly impossible to reproduce deterministically.&lt;/p&gt;
&lt;p&gt;The Go race detector is one of the most powerful debugging tools in any language ecosystem. It instruments every memory access at the compiler level and reports races precisely: the exact goroutine stacks at the moment of the conflicting accesses. But it only helps if you run it. Most teams run it once after a bug report. The right approach is running it on every PR, in CI, before the code is ever merged.&lt;/p&gt;</description></item><item><title>Lesson 5: CI/CD for Go — GitHub Actions that actually catch bugs</title><link>/post/go/go-deploy-cicd/</link><pubDate>Sun, 15 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-cicd/</guid><description>&lt;p&gt;I&amp;rsquo;ve set up CI pipelines for Go services about a dozen times, and every iteration taught me something about what the pipeline should actually catch. The first version ran &lt;code&gt;go test ./...&lt;/code&gt; and called it done. That caught compilation errors and test failures, but not data races, not staticcheck warnings, not formatting drift, not license issues, not security vulnerabilities in dependencies. Each of those failure categories has caused a production incident at some point. The current version catches most of them before the PR is merged.&lt;/p&gt;</description></item><item><title>Lesson 4: Config Injection — Environment variables, flags, files — in that order</title><link>/post/go/go-deploy-config-injection/</link><pubDate>Sat, 02 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-config-injection/</guid><description>&lt;p&gt;Configuration management is one of those topics that seems solved until you maintain a service that runs in four different environments, has twenty configurable parameters, and needs its secrets rotated without a redeployment. I&amp;rsquo;ve gone through several evolutionary stages on this: hardcoded values (the naive phase), a single &lt;code&gt;config.yaml&lt;/code&gt; file, then twelve &lt;code&gt;config-{env}.yaml&lt;/code&gt; files, and finally landing on what the 12-factor app methodology describes — environment variables as the source of truth for deployment-specific configuration.&lt;/p&gt;</description></item><item><title>Lesson 3: Health Checks — Readiness vs liveness, and why both matter</title><link>/post/go/go-deploy-health-checks/</link><pubDate>Sun, 22 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-health-checks/</guid><description>&lt;p&gt;I&amp;rsquo;ve seen two types of health check implementations in production: the ones that always return 200 OK, and the ones that actually check something. The first kind is theater — Kubernetes thinks the pod is healthy and routes traffic to it, but the pod is actually deadlocked or its database connection pool is exhausted. The second kind is what saves you at 3am.&lt;/p&gt;
&lt;p&gt;Health checks are Kubernetes&amp;rsquo;s mechanism for deciding when a pod is ready to receive traffic and when it needs to be restarted. Get them right and Kubernetes becomes a genuinely reliable self-healing system. Get them wrong and you have an elaborate system that sends traffic to broken pods and restarts healthy ones.&lt;/p&gt;</description></item><item><title>Lesson 2: Docker for Go — Multi-stage builds that produce tiny images</title><link>/post/go/go-deploy-docker/</link><pubDate>Sat, 10 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-docker/</guid><description>&lt;p&gt;The first Dockerfile I wrote for a Go service produced a 1.2 GB image. It was based on &lt;code&gt;golang:1.22&lt;/code&gt;, which includes the full Go toolchain, build cache, test infrastructure, and a complete Debian system. The compiled binary was 18 MB. I was shipping 1.2 GB to run 18 MB.&lt;/p&gt;
&lt;p&gt;The second version, after learning about multi-stage builds, produced a 22 MB image. Same binary. No compiler. No package manager. No shell. Just the binary and the absolute minimum needed to run it. The difference matters not just for storage: smaller images pull faster, have smaller attack surfaces, and fail more obviously when a required file is missing.&lt;/p&gt;</description></item><item><title>Lesson 1: Static Binaries — One file, zero dependencies, deploy anywhere</title><link>/post/go/go-deploy-static-binaries/</link><pubDate>Sun, 30 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-static-binaries/</guid><description>&lt;p&gt;The first time I deployed a Go binary to a production server, I was braced for the usual ceremony: SSH in, install the right runtime version, copy over config files, fiddle with symlinks. Instead, I ran &lt;code&gt;scp&lt;/code&gt; and then the binary, and it just worked. No installation. No dependency hell. No &amp;ldquo;but it works on my machine.&amp;rdquo; The binary contained everything it needed.&lt;/p&gt;
&lt;p&gt;This is Go&amp;rsquo;s single greatest operational advantage: the compiler produces a self-contained executable. Understanding exactly how this works — and how it can break — makes the difference between a deploy that takes five minutes and one that takes an afternoon.&lt;/p&gt;</description></item></channel></rss>