<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Production Release and Recovery on StackOnward</title><link>https://stackonward.com/series/production-release-and-recovery/</link><description>Recent content in Production Release and Recovery on StackOnward</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Fri, 11 Sep 2026 05:18:00 +0800</lastBuildDate><atom:link href="https://stackonward.com/series/production-release-and-recovery/index.xml" rel="self" type="application/rss+xml"/><item><title>Automatic rollback for a failed deployment: transaction state, traffic switch, and rollback limits</title><link>https://stackonward.com/posts/automatic-deployment-recovery/</link><pubDate>Fri, 11 Sep 2026 05:00:00 +0800</pubDate><guid>https://stackonward.com/posts/automatic-deployment-recovery/</guid><description>&lt;p&gt;Automatic rollback for a failed deployment is not &lt;code&gt;docker compose down&lt;/code&gt; after the script dies. Recovery depends on three facts: whether the candidate already took public traffic, whether the previous upstream can still be restored, and whether business checks finished. If the previous version is still serving, stop only this candidate. If the traffic switch may be half-done, restore the previous upstream. If the new version is still in the observation window, switch back. Recovery should finish the commit records only when the release has already entered the commit phase.&lt;/p&gt;</description></item><item><title>Docker Compose Blue-Green: Health Check, Switch, Drain</title><link>https://stackonward.com/posts/single-server-short-downtime-deployment/</link><pubDate>Fri, 11 Sep 2026 05:00:00 +0800</pubDate><guid>https://stackonward.com/posts/single-server-short-downtime-deployment/</guid><description>&lt;p&gt;A Docker Compose blue-green deployment on one server starts the candidate, waits until the app is ready, validates and switches the Caddy upstream, proves the public hostname, drains old connections, then stops the old version. Do not delete the old container before the candidate is taking traffic.&lt;/p&gt;
&lt;p&gt;That sequence can shrink the unavailable window. The host, disk, Docker daemon, and shared database are still single points of failure, so measure the window instead of calling the result zero downtime.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/single-server-short-downtime-deployment/en/blue-green-release-flow.svg" alt="Old version keeps traffic on 18080; candidate starts on 18081 and passes a loopback health check; Caddy switches, the public hostname is verified, then old connections drain" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;The gateway switch is the only public cutover. Candidate startup and warmup happen before that switch.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="docker-compose-blue-green-only-works-under-these-constraints"&gt;Docker Compose blue-green only works under these constraints&lt;/h2&gt;
&lt;p&gt;Run two app instances on one host only when the service meets all of these conditions:&lt;/p&gt;</description></item><item><title>Why Pin a Docker Image Digest: Reproducible Deploys and Rollbacks</title><link>https://stackonward.com/posts/docker-image-digest-pinning/</link><pubDate>Fri, 11 Sep 2026 05:00:00 +0800</pubDate><guid>https://stackonward.com/posts/docker-image-digest-pinning/</guid><description>&lt;p&gt;Pin a Docker image digest by writing the production image as &lt;code&gt;repository:tag@sha256:digest&lt;/code&gt;. The version tag shows which release you planned. The digest is the content Docker must pull. &lt;code&gt;latest&lt;/code&gt;, &lt;code&gt;stable&lt;/code&gt;, or a movable version tag cannot guarantee that a rebuild or rollback returns the original image.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/docker-image-digest-pinning/en/image-identity-chain.svg" alt="A movable tag points to an OCI index digest, which selects amd64 or arm64 platform manifests, and the running container records the actual image" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Pinning the index digest freezes the platform set. Each node still pulls the manifest that matches its architecture.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="why-pin-a-docker-image-digest-instead-of-a-moving-tag"&gt;Why pin a Docker image digest instead of a moving tag&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.docker.com/dhi/explore/security-concepts/digests/" target="_blank" rel="noopener"&gt;Docker&amp;rsquo;s image digest documentation&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; defines a digest as a SHA-256 identifier of image content. A registry maintainer can retarget the same tag to a patched image. The original digest does not move with the tag.&lt;/p&gt;</description></item><item><title>OneDev Tutorial: Docker Compose, Git, and CI/CD on One Server</title><link>https://stackonward.com/posts/onedev-production-deployment/</link><pubDate>Fri, 11 Sep 2026 04:24:00 +0800</pubDate><guid>https://stackonward.com/posts/onedev-production-deployment/</guid><description>&lt;p&gt;A OneDev tutorial starts with one instance that can host Git, review, issues, CI/CD, artifacts, and packages. Keep code on a server you operate, and skip running a separate Git service, CI, and package registry, when that trade-off is worth the ops work. The official single-node baseline is also lower than full GitLab.&lt;/p&gt;
&lt;p&gt;The environment is one 64-bit Linux host: OneDev and PostgreSQL run under Docker Compose, and Caddy terminates HTTPS. After the instance is ready, create a Go project and prove push, tests, image build, production deploy, backup, restore, and upgrade. A container in &lt;code&gt;running&lt;/code&gt; is not enough. The repository, Git SSH, the pipeline, and restore still have to work.&lt;/p&gt;</description></item><item><title>When to Run Database Migrations: Expand, Migrate, Contract</title><link>https://stackonward.com/posts/database-migration-release-order/</link><pubDate>Fri, 11 Sep 2026 04:10:00 +0800</pubDate><guid>https://stackonward.com/posts/database-migration-release-order/</guid><description>&lt;p&gt;When to run database migrations depends on whether the current schema can serve both the old and the new application. Add compatible structure first, ship the new app, then backfill data. Drop columns, finish renames, and tighten constraints in a later release after the old version has left.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/database-migration-release-order/en/expand-migrate-contract.svg" alt="Expand adds compatible structure, Migrate deploys compatible apps and backfills in batches, Contract drops old structure after old consumers exit" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Destructive DDL is not tied to the first rollout of new code.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="when-to-run-database-migrations-depends-on-compatibility"&gt;When to run database migrations depends on compatibility&lt;/h2&gt;
&lt;p&gt;The same DDL can wait on a lock for seconds or for minutes depending on table size, PostgreSQL major version, and live writes. Confirm those facts, plus how old and new code touch the table, before choosing a stage.&lt;/p&gt;</description></item></channel></rss>