Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
End-to-End Zero Downtime: Deploying Bookstore with Blue-Green and Canary via GitHub Actions
All pieces of the series in a single GitHub Actions workflow: build the image once, a migration gate and an expand migration bundle, deploy to the idle color, a smoke test through the preview, 10% and 50% canary gates, a full switch and a manual rollback job. Run locally with act, the pipeline failed four times before succeeding: a gate scanning Down(), act's artifact v7 incompatibility, a preview pointing at the old color, and too little canary traffic.
Rollback or Roll Forward? Rollback Plans, Kill Switches and Automated Rollback Criteria
There are four ways back: kill switch, artifact rollback, roll forward and limiting the impact; the choice depends on flags, the readiness of the previous artifact and schema compatibility. An atomic symlink switch, a Down migration that restores the column but not its 300,004 names, git revert of a merge commit, an automated trigger rolling back a 20%-failing version in 10 s (18 of 85 requests failed), a runbook template and measuring with DORA.
Feature Flags: Decoupling Deploy from Release with Microsoft.FeatureManagement and OpenFeature
A feature flag lets you deploy code and release behavior with a config change. Targeting with Microsoft.FeatureManagement 4.8 (10.1% of 1000 users, sticky per user), variants, WithFeatureGate on minimal APIs and a kill switch that takes effect in 0.4 s; a signal-based flag service and a tested structural directive in Angular; the targeting boolean flag returning false through OpenFeature's preview provider, and flag debt.
Where Migrations Fit in a Deploy: Expand-Contract, EF Core Migration Bundles and Backward-Compatible Schemas
Safely splitting a column while two versions run side by side takes three deploys: add and dual-write, switch to the new columns, remove the old one. Real runs on PostgreSQL with 300,000 rows: v1 and v2 working together on one schema, the transaction error of a CONCURRENTLY index, deadlocks in parallel bundles because Npgsql's migration lock is tied to the transaction, 135 ms batches instead of an UPDATE locking for 8 s, and a review gate that stops DROP COLUMN.
Canary Releases: Shifting Traffic by Percentage, Deciding with Metrics and Automated Analysis
A canary runs the new version with a small share of traffic and decides by comparing it with a control running at the same time. Weights in Nginx (900/100, not sticky), a user-sticky canary with split_clients and the empty-key trap, sticky cookies in 1.30; 4.80% vs 0% over 4,000 requests but a meaningless result over 19, a gate returning WAIT/ABORT/PROMOTE, Gateway API weights, and Argo Rollouts aborting a faulty version automatically.
Blue-Green Deployment: Two Environments with Nginx, App Service Slots and Instant Switchback
Blue-green installs the new version on the idle environment, runs a smoke test and switches traffic with a single reload; going back is the same reload. A preview port in Nginx, a smoke test with a version check, an error-free probe through switch and switchback, a 4 s request finishing on blue during the reload; App Service slot swap warm-up and slot setting pitfalls, a Service selector switch in Kubernetes, and the limit a shared database imposes.
Rolling Deployments: Updating Servers One by One, maxSurge and maxUnavailable in Kubernetes
A rolling deployment updates the fleet piece by piece; done right there is no outage, but two versions live side by side for a while. Three instances behind Nginx: a naive script dropped 2 of 90 GETs, while a script with drain and readiness checks passed 237 GETs and 90 POSTs without errors. Choosing maxSurge/maxUnavailable and minReadySeconds in Kubernetes, why exceeding progressDeadlineSeconds doesn't roll back, and when rolling is the wrong choice.
Prerequisites for Zero Downtime: Graceful Shutdown, Readiness, Stateless Apps and Backward Compatibility
No deployment strategy can save an app that doesn't finish its work when stopping and doesn't tell the truth about being ready. With real runs: a 5 s request is cut with an RST when ShutdownTimeout is 2 s, Docker's 1 s wait produces exit code 137 (SIGKILL), readiness returns 503 during the drain while the API keeps serving 200, separate Data Protection key rings throw CryptographicException, and a deleted Angular chunk turns into a ChunkLoadError.
Deploy vs Release: Why Downtime Happens and a Map of Deployment Strategies
A deploy installs code in an environment, a release is when users see the new behavior; teams that can separate the two split the risk. In a single-container recreate deploy behind Nginx, 12 requests failed within 2.5 seconds. The five sources of downtime, a map of recreate, rolling, blue-green, canary, shadow, A/B and feature flags, a decision table, and the risk = blast radius × detection time calculation.
Reading Old GitHub Actions Workflows: From set-output to GITHUB_OUTPUT, From Node 12/16/20 to Node 24
When updating an old workflow, change the runner label, action majors, SDK source, workflow commands and cache key together: ubuntu-18.04, @v2 actions and ::set-output either no longer work or warn in 2026. A 2018–2026 timeline, the Node 12→24 transitions, actionlint and act output for a 2021-style .NET 5 workflow, a checklist for moving to .NET 10 and a summary of 2026.
Debugging, Speed and Cost in GitHub Actions: Debug Logging, act, Cache Strategy and Artifact Retention
For debugging, turn on ACTIONS_STEP_DEBUG/ACTIONS_RUNNER_DEBUG for a single re-run, shorten the loop with act, build cache keys from the lock file hash with restore-keys, and keep artifacts briefly. Real cache miss, hit and restore-keys output from three act runs, a list of act's limits, the 10 GB quota and 7-day rule, retention, billing screens and a speed checklist.
GitHub Actions Runners: Hosted Images, the ubuntu-latest Migration, ARM, Larger Runners and Self-Hosted
Pin the runner label in release and deploy pipelines: ubuntu-latest moves from 24.04 to 26.04 between October 19 and November 19, 2026. The hosted image map, arm64 and larger runners, actionlint's label check, why a label in act is only a Docker image mapping, ephemeral self-hosted runners, the minimum version rule, ARC, 2026 prices and how multi-cloud affects IP allowlists.