Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
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.
GitHub Actions Security: SHA Pinning, Lockfiles, pull_request_target and Preventing Script Injection
Pin actions to full commit SHAs and keep them current with Dependabot and lockfiles, pass untrusted values through env: instead of run:, and never run PR code in pull_request_target. The tj-actions case, zizmor and actionlint findings, gh actions-lock, real script injection output under act, checkout@v7 and the December 2, 2026 change, a safe workflow_run pattern and read-only cache tokens.
Reducing Duplication in GitHub Actions: Composite Actions, Reusable Workflows and YAML Anchors
Use a composite action to share steps, a reusable workflow to share jobs that need environments and approvals, and YAML anchors to repeat a block within one file. Bookstore's dotnet-ci action and reusable-deploy workflow under act; the risks of secrets: inherit, environment secrets, the 10-level/50-workflow limits, the $/ syntax, versioning and why merge keys are not supported.
Deploying with GitHub Actions 2: Your Own Linux Server via SSH, Docker Images and GHCR
Build the image once in GitHub Actions, push it to GHCR and send only the digest to the server over SSH; the server pulls it with Docker Compose and restarts. metadata-action, build-push-action and the gha cache backend (28 s down to 4 s), actions/attest, pinning known_hosts and what happens when the host key changes, rollback with .env.previous; real runs with act.
Deploying with GitHub Actions 1: Azure App Service with OIDC and Secretless Cloud Access
With OIDC, every job gets a signed, short-lived token from GitHub, and Azure verifies it against a federated credential and grants access without a client secret. id-token: write, sub claim formats, the July 2026 subject change with IDs, custom property claims, deploying to a staging slot, smoke testing and swapping; AWS and Google Cloud equivalents.
GitHub Actions Environments: Protection Rules, Approvals and Deployment History
An environment is a named target that applies protection rules (required reviewers, wait timers, branch/tag policies) before a job reaches a runner, exposes its secrets only to that job and keeps a deployment history. Bookstore's staging/production deploy workflow, plan limits for private repositories, deployment: false, per-environment concurrency and queue: max; where act and actionlint stop, with real output.