[{"data":1,"prerenderedAt":1123},["ShallowReactive",2],{"navigation":3,"/blog/branch-theater-staging-branches":150,"surround-branch-theater-staging-branches":1110},[4],{"title":5,"path":6,"stem":7,"children":8,"page":149},"Blog","/blog","blog",[9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97,101,105,109,113,117,121,125,129,133,137,141,145],{"title":10,"path":11,"stem":12},"How to Increase Free Tier Memory on AWS EC2","/blog/aws-ec2-ram","blog/aws-ec2-ram",{"title":14,"path":15,"stem":16},"Blooper: The Disappearing Cloudflare Variables","/blog/blooper-cloudflare-env-vars","blog/blooper-cloudflare-env-vars",{"title":18,"path":19,"stem":20},"Blooper: Hardcoded Content Links cause 404s","/blog/blooper-hardcoded-nuxt-content-links","blog/blooper-hardcoded-nuxt-content-links",{"title":22,"path":23,"stem":24},"Blooper: The Invisible SEO","/blog/blooper-nuxt-seo-env","blog/blooper-nuxt-seo-env",{"title":26,"path":27,"stem":28},"Blooper: The SSR Crash of Doom","/blog/blooper-ssr-feedback-form","blog/blooper-ssr-feedback-form",{"title":30,"path":31,"stem":32},"Blooper: The YouTube Crawl Budget Killer","/blog/blooper-video-crawl-budget","blog/blooper-video-crawl-budget",{"title":34,"path":35,"stem":36},"Blooper: PWA Redirecting SPA Routes","/blog/blooper-vite-pwa-ssr-redirects","blog/blooper-vite-pwa-ssr-redirects",{"title":38,"path":39,"stem":40},"Git Branching in 2026: Code State, Environment Isolation, and Modern CD","/blog/branch-theater-staging-branches","blog/branch-theater-staging-branches",{"title":42,"path":43,"stem":44},"The Developer's Guide to Avoiding Cloud Billing Traps in 2026","/blog/cloud-billing-traps-2026","blog/cloud-billing-traps-2026",{"title":46,"path":47,"stem":48},"Disk Management in Linux","/blog/disk-management-in-linux","blog/disk-management-in-linux",{"title":50,"path":51,"stem":52},"Docker Devtools Stage | Powerlevel, OhMyZSH, tmux, fuzzy finder, autocomplete etc","/blog/docker-devtools","blog/docker-devtools",{"title":54,"path":55,"stem":56},"Docker container | Resolving Permission Denied Woes: A Decade-Long Struggle Unraveled","/blog/docker-non-root-user-perms","blog/docker-non-root-user-perms",{"title":58,"path":59,"stem":60},"The Ultimate Guide to Free Tier Hosting Platforms (Serverless & Containers)","/blog/free-hosting-platforms-guide","blog/free-hosting-platforms-guide",{"title":62,"path":63,"stem":64},"Git commands | underrated","/blog/git-commands-underrated","blog/git-commands-underrated",{"title":66,"path":67,"stem":68},"Journey: Taming Edge Caching and KV Storage","/blog/journey-github-streak-caching","blog/journey-github-streak-caching",{"title":70,"path":71,"stem":72},"Kubernetes Cluster Setup Guide","/blog/k8s-setup","blog/k8s-setup",{"title":74,"path":75,"stem":76},"Linux WiFi Drivers","/blog/linux-wifi-drivers","blog/linux-wifi-drivers",{"title":78,"path":79,"stem":80},"4 Common Routing Traps When Migrating to Nuxt i18n","/blog/nuxt-i18n-routing","blog/nuxt-i18n-routing",{"title":82,"path":83,"stem":84},"pfSense basic firewall setup","/blog/pfsense-basic-firewall-setup","blog/pfsense-basic-firewall-setup",{"title":86,"path":87,"stem":88},"pfSense Quickstart","/blog/pfsense-quickstart","blog/pfsense-quickstart",{"title":90,"path":91,"stem":92},"Postmortem: Cloudflare Worker Missing Dependency on Deploy","/blog/postmortem-cloudflare-worker-dependency-deploy","blog/postmortem-cloudflare-worker-dependency-deploy",{"title":94,"path":95,"stem":96},"Postmortem: Serverless HTTP Driver Transaction Failures","/blog/postmortem-neon-http-transactions","blog/postmortem-neon-http-transactions",{"title":98,"path":99,"stem":100},"Postmortem: The Ghost in the Chrome Cache (Nuxt & Cloudflare Pages)","/blog/postmortem-nuxt-cloudflare-chunk-error","blog/postmortem-nuxt-cloudflare-chunk-error",{"title":102,"path":103,"stem":104},"Postmortem: Nuxt Content 404s on Staging Deployments","/blog/postmortem-nuxt-content-404-staging","blog/postmortem-nuxt-content-404-staging",{"title":106,"path":107,"stem":108},"Postmortem: 500 Internal Server Error on ISR Routes","/blog/postmortem-nuxt-content-payload-extraction","blog/postmortem-nuxt-content-payload-extraction",{"title":110,"path":111,"stem":112},"Post-Mortem: Intrusive Database Driver Refactoring","/blog/postmortem-proxy-re-architecting","blog/postmortem-proxy-re-architecting",{"title":114,"path":115,"stem":116},"Post-Mortem: Cloudflare Worker Connection Hang & Resilient DB Fallbacks","/blog/postmortem-worker-db-connection-hang","blog/postmortem-worker-db-connection-hang",{"title":118,"path":119,"stem":120},"Pro Tip: Sandboxing Cloudflare Workers for AI Agents","/blog/pro-tip-wrangler-local-dev-trick","blog/pro-tip-wrangler-local-dev-trick",{"title":122,"path":123,"stem":124},"Proxmox DNS Management","/blog/proxmox-dns","blog/proxmox-dns",{"title":126,"path":127,"stem":128},"Proxmox Recovery","/blog/proxmox-recovery","blog/proxmox-recovery",{"title":130,"path":131,"stem":132},"Proxmox WiFi Connection Story","/blog/proxmox-wifi","blog/proxmox-wifi",{"title":134,"path":135,"stem":136},"roadauth-rails api jwt cors","/blog/rodauth-jwt","blog/rodauth-jwt",{"title":138,"path":139,"stem":140},"SOCKS5 Proxy Quickstart","/blog/sock5-proxy","blog/sock5-proxy",{"title":142,"path":143,"stem":144},"tmux status bar toggle","/blog/tmux-status","blog/tmux-status",{"title":146,"path":147,"stem":148},"UFW - Cheatsheet","/blog/ufw-cheatsheet","blog/ufw-cheatsheet",false,{"id":151,"title":38,"authors":152,"badge":158,"body":160,"date":1098,"description":1099,"extension":1100,"image":1101,"imageComponent":1102,"meta":1104,"navigation":649,"path":39,"pinned":649,"seo":1106,"seoImage":1107,"stem":40,"__hash__":1109},"posts/blog/branch-theater-staging-branches.md",[153],{"name":154,"to":155,"avatar":156},"Rahul Dhole","/",{"src":157},"/profile.png",{"label":159},"Git & DevOps",{"type":161,"value":162,"toc":1083},"minimark",[163,172,190,193,196,199,202,207,217,257,260,270,281,286,304,306,310,322,325,327,331,338,341,346,349,355,384,387,393,397,400,407,414,418,424,444,453,456,462,464,468,483,502,505,508,515,518,521,523,527,530,534,540,551,554,557,562,832,841,843,847,852,864,867,899,902,904,908,1067,1069,1072,1079],[164,165,166,167,171],"p",{},"We have all been there. You run ",[168,169,170],"code",{},"git log --graph --oneline",", see a tangled mess of branches weaving in and out, and decide it is time to tidy things up. Everything should be a clean, elegant, straight line.",[164,173,174,175,178,179,182,183,186,187,189],{},"So you spin up three permanent branches: ",[168,176,177],{},"develop",", ",[168,180,181],{},"staging",", and ",[168,184,185],{},"main",". You write code on ",[168,188,177],{},", rebase things whenever history diverges, fast-forward your releases down the line, and admire your spotless commit tree.",[164,191,192],{},"Then you hook that repo up to modern CI/CD pipelines, and the whole setup falls apart.",[164,194,195],{},"Your database migrations fall out of sync. A broken feature sitting on staging blocks a critical hotfix to production. And someone on the team ends up in rebase conflict hell over code that was thrown out two weeks ago.",[164,197,198],{},"Using long-lived Git branches as the primary mechanism for representing deployment environments creates unnecessary coupling between code state and environment state. Here is why permanent environment branches fail to provide true isolation, how persistent state ruins the party, and what actual production workflows look like.",[200,201],"hr",{},[203,204,206],"h2",{"id":205},"_1-the-branch-theater-illusion","1. The Branch Theater Illusion",[164,208,209,210,178,212,182,214,216],{},"If your ",[168,211,177],{},[168,213,181],{},[168,215,185],{}," branches sit in a single straight line, one of two things is happening behind the scenes:",[218,219,220,242],"ol",{},[221,222,223,227,228,231,232,234,235,237,238,241],"li",{},[224,225,226],"strong",{},"You are rebasing permanent branches."," You run ",[168,229,230],{},"git rebase"," on ",[168,233,181],{}," or ",[168,236,185],{},". That rewrites commit hashes, destroys your audit trail, and forces a ",[168,239,240],{},"git push --force",". The second another developer pulls that branch or a CI runner checks it out, history breaks.",[221,243,244,247,248,250,251,253,254,256],{},[224,245,246],{},"You are playing \"Branch Theater\"."," You commit on ",[168,249,177],{},", fast-forward ",[168,252,181],{}," to match it, and then fast-forward ",[168,255,185],{}," to match that.",[164,258,259],{},"In the second scenario, your Git tree looks like this:",[261,262,268],"pre",{"className":263,"code":265,"language":266,"meta":267},[264],"language-text","A --- B --- C --- D --- E (develop, staging, main)\n","text","",[168,269,265],{"__ignoreMap":267},[164,271,272,273,276,277,280],{},"If all three branches point to commit ",[168,274,275],{},"E",", you do not have three different ",[224,278,279],{},"code states",". You just have three bookmarks pointing to the exact same commit in your repository history.",[282,283],"mermaid",{":config":284,"code":285},"config","flowchart%20LR%0A%20%20%20%20E%5B%22Commit%20E%22%5D%0A%20%20%20%20E%20--%3E%20D1%5B%22develop%22%5D%0A%20%20%20%20E%20--%3E%20S1%5B%22staging%22%5D%0A%20%20%20%20E%20--%3E%20M1%5B%22main%22%5D",[164,287,288,289,291,292,294,295,297,298,300,301,303],{},"While ",[168,290,181],{}," and ",[168,293,185],{}," may point to separate cloud infrastructure and database instances, having three identical branches provides zero isolation for feature development. A staging branch is only useful if it can hold a release candidate in isolation while you keep committing new, unverified experiments on ",[168,296,177],{},". If ",[168,299,181],{}," is always identical to ",[168,302,177],{},", maintaining separate environment branches is just pure ceremony.",[200,305],{},[203,307,309],{"id":308},"_2-environment-branches-vs-real-isolation","2. Environment Branches vs. Real Isolation",[164,311,312,313,178,315,178,318,321],{},"The original Gitflow specification was published in 2010. It was designed primarily for versioned software releases—like desktop software, mobile builds, or scheduled release packages—where distinct version lines (",[168,314,177],{},[168,316,317],{},"release/*",[168,319,320],{},"hotfix/*",") had long lifecycles.",[164,323,324],{},"In modern continuous delivery, however, teams often misuse Gitflow by treating Git branches as environment proxies. Trying to manage deployment environments purely through Git branches creates three fundamental problems:",[200,326],{},[203,328,330],{"id":329},"_3-the-preview-bottlenecks-nobody-mentions","3. The Preview Bottlenecks Nobody Mentions",[164,332,333,334],{},"Hosting platforms love to sell us on this pitch: ",[335,336,337],"em",{},"\"Just push your branch and get an instant preview URL!\"",[164,339,340],{},"For stateless frontends with zero backend dependencies, that works like magic. But the second your app connects to a real database and third-party APIs, you run into real architectural headaches:",[342,343,345],"h3",{"id":344},"bottleneck-a-the-poisoned-staging-database","Bottleneck A: The Poisoned Staging Database",[164,347,348],{},"Code is easy to throw away. Databases are not. Database state does not magically rewind when Git rewinds.",[164,350,351,352,354],{},"Most teams hook their permanent ",[168,353,181],{}," branch to a single shared staging database. Here is how that bites you:",[218,356,357,363,369,372,378],{},[221,358,359,360,362],{},"You create a new database migration on ",[168,361,177],{}," for a feature you want to test.",[221,364,365,366,368],{},"You push it to ",[168,367,181],{}," so you can click around the preview URL.",[221,370,371],{},"Your deployment pipeline runs your migration script (Drizzle, Prisma, Flyway) against the staging database.",[221,373,374,375],{},"You open the preview, test it, and realize: ",[335,376,377],{},"\"The schema design is flawed. I need to redo it.\"",[221,379,380,381,383],{},"You go back to ",[168,382,177],{}," and edit or delete that migration file.",[164,385,386],{},"Too late. The staging database already executed that old migration and logged its hash in the migration table. The staging DB is now poisoned. Anyone else trying to deploy to staging will hit migration errors until someone manually logs into the database to drop tables or undo the ledger.",[261,388,391],{"className":389,"code":390,"language":266,"meta":267},[264],"[develop branch]  --> (Local DB)\n[staging branch]  --> [Shared Staging DB]  \u003C-- Poisoned by test migrations!\n[main branch]     --> [Production DB]\n",[168,392,390],{"__ignoreMap":267},[342,394,396],{"id":395},"bottleneck-b-the-static-webhook-wall","Bottleneck B: The Static Webhook Wall",[164,398,399],{},"Modern apps connect to third-party APIs: Stripe for billing, Clerk for auth, Resend for email, GitHub for OAuth.",[164,401,402,403,406],{},"Almost every single one of them expects a fixed, registered webhook callback URL, such as ",[168,404,405],{},"https://staging-api.yoursite.com/webhooks/stripe",".",[164,408,409,410,413],{},"When you create an ephemeral branch deployment, you get a dynamic URL like ",[168,411,412],{},"https://app-feat-checkout-x89f2.pages.dev",". Stripe has no idea that URL exists. Unless you build a custom proxy system just to route webhooks to random preview URLs, you are forced to use a permanent staging URL anyway.",[342,415,417],{"id":416},"bottleneck-c-the-staging-traffic-jam","Bottleneck C: The Staging Traffic Jam",[164,419,420,421,423],{},"Say you use a single ",[168,422,181],{}," branch for previews:",[425,426,427,433,438,441],"ul",{},[221,428,429,430,432],{},"You merge Feature A into ",[168,431,181],{}," for review.",[221,434,435,436,406],{},"You also merge Feature B (a quick bugfix) into ",[168,437,181],{},[221,439,440],{},"QA tests both, but Feature A has a critical bug and cannot ship.",[221,442,443],{},"Feature B works great and needs to go to production immediately.",[164,445,446,447,449,450,452],{},"Now you are stuck. You cannot merge ",[168,448,181],{}," into ",[168,451,185],{}," because you would accidentally ship broken Feature A. You now have to spend an afternoon cherry-picking commits and untangling Git history under pressure.",[282,454],{":config":284,"code":455},"gitGraph%0A%20%20%20commit%20id%3A%20%22main%22%0A%0A%20%20%20branch%20feat%2FA%0A%20%20%20commit%20id%3A%20%22Feature%20A%22%0A%20%20%20checkout%20main%0A%0A%20%20%20branch%20feat%2FB%0A%20%20%20commit%20id%3A%20%22Feature%20B%22%0A%20%20%20checkout%20main%0A%0A%20%20%20branch%20staging%0A%20%20%20checkout%20staging%0A%20%20%20merge%20feat%2FA%20id%3A%20%22A%20on%20staging%22%0A%20%20%20merge%20feat%2FB%20id%3A%20%22B%20on%20staging%22",[261,457,460],{"className":458,"code":459,"language":266,"meta":267},[264],"staging = A + B\nmain    = neither\n\nA is broken.\nB is good.\n\nYou cannot promote staging without also promoting A.\n",[168,461,459],{"__ignoreMap":267},[200,463],{},[203,465,467],{"id":466},"_4-code-promotion-vs-artifact-promotion","4. Code Promotion vs. Artifact Promotion",[164,469,470,471,473,474,473,476,478,479,482],{},"The biggest flaw in relying on environment branches (",[168,472,177],{}," $\\to$ ",[168,475,181],{},[168,477,185],{},") is that you end up ",[224,480,481],{},"rebuilding your code"," at every step.",[164,484,485,486,449,488,490,491,449,493,495,496,499,500,406],{},"When you merge ",[168,487,177],{},[168,489,181],{},", your CI pipeline builds a new Docker container or JavaScript bundle. When you later merge ",[168,492,181],{},[168,494,185],{},", your CI pipeline builds ",[335,497,498],{},"yet another"," binary from ",[168,501,185],{},[164,503,504],{},"Even when the source and dependency inputs are identical, rebuilding creates another artifact instead of promoting the exact artifact that passed staging. Build-once pipelines eliminate that unnecessary distinction and give you a direct chain from tested artifact to production artifact.",[282,506],{":config":284,"code":507},"flowchart%20LR%0A%20%20%20%20C%5B%22Git%20Commit%22%5D%20--%3E%20B1%5B%22Build%20%231%22%5D%0A%20%20%20%20B1%20--%3E%20S%5B%22Staging%22%5D%0A%0A%20%20%20%20S%20--%3E%20M%5B%22Merge%20to%20main%22%5D%0A%20%20%20%20M%20--%3E%20B2%5B%22Build%20%232%22%5D%0A%20%20%20%20B2%20--%3E%20P%5B%22Production%22%5D%0A%0A%20%20%20%20style%20B1%20stroke-width%3A2px%0A%20%20%20%20style%20B2%20stroke-width%3A2px",[164,509,510,511,514],{},"A mature Continuous Delivery pipeline promotes ",[224,512,513],{},"build artifacts",", not Git branches:",[282,516],{":config":284,"code":517},"flowchart%20LR%0A%20%20%20%20C%5B%22Git%20Commit%22%5D%20--%3E%20B%5B%22Build%20Once%22%5D%0A%20%20%20%20B%20--%3E%20S%5B%22Staging%22%5D%0A%20%20%20%20S%20--%3E%20P%5B%22Production%22%5D",[164,519,520],{},"By decoupling code state, environment state, and deployment artifacts, your environments reflect actual verified builds rather than branch merge gymnastics.",[200,522],{},[203,524,526],{"id":525},"_5-the-two-strategies-that-actually-work","5. The Two Strategies That Actually Work",[164,528,529],{},"Stop trying to rebase deployment branches. Here are two practical ways to structure your workflow today.",[342,531,533],{"id":532},"option-a-lean-environment-promotion-forward-only","Option A: Lean Environment Promotion (Forward-Only)",[164,535,536,539],{},[335,537,538],{},"Best when:"," You rely on persistent preprod infrastructure, shared staging databases, or fixed third-party webhooks.",[164,541,542,543,546,547,550],{},"Rather than attempting to maintain a linear history across environment branches, accept that Git is a Directed Acyclic Graph (DAG) designed to track branching paths. Move code in one direction only: ",[224,544,545],{},"develop $\\to$ staging $\\to$ main",", using explicit merge commits (",[168,548,549],{},"--no-ff",").",[282,552],{":config":284,"code":553},"---%0Atitle%3A%20Forward-Only%20Deployment%20Pipeline%0A---%0AgitGraph%0A%20%20%20commit%20id%3A%20%22init%22%0A%20%20%20branch%20staging%0A%20%20%20branch%20develop%0A%0A%20%20%20%25%25%20Feature%201%0A%20%20%20checkout%20develop%0A%20%20%20branch%20feat%2Fbilling%0A%20%20%20commit%20id%3A%20%22add%20migration%22%0A%20%20%20commit%20id%3A%20%22add%20checkout%20UI%22%0A%20%20%20checkout%20develop%0A%20%20%20merge%20feat%2Fbilling%20id%3A%20%22merge%20feat%2Fbilling%22%0A%0A%20%20%20%25%25%20Promote%20to%20staging%20(Preview%20%26%20DB%20Migration)%0A%20%20%20checkout%20staging%0A%20%20%20merge%20develop%20id%3A%20%22release%20staging-rc1%22%0A%0A%20%20%20%25%25%20Promote%20to%20production%20(Prod%20Deploy%20%26%20DB%20Migration)%0A%20%20%20checkout%20main%0A%20%20%20merge%20staging%20id%3A%20%22release%20v1.0.0%22%20tag%3A%20%22v1.0.0%22%0A%0A%20%20%20%25%25%20Feature%202%20while%20main%20stays%20clean%0A%20%20%20checkout%20develop%0A%20%20%20branch%20feat%2Fnotifications%0A%20%20%20commit%20id%3A%20%22add%20email%20alerts%22%0A%20%20%20checkout%20develop%0A%20%20%20merge%20feat%2Fnotifications%20id%3A%20%22merge%20feat%2Fnotifications%22%0A%0A%20%20%20%25%25%20Promote%20Feature%202%0A%20%20%20checkout%20staging%0A%20%20%20merge%20develop%20id%3A%20%22release%20staging-rc2%22%0A%0A%20%20%20checkout%20main%0A%20%20%20merge%20staging%20id%3A%20%22release%20v1.1.0%22%20tag%3A%20%22v1.1.0%22",[164,555,556],{},"Those merge commits serve as clear release checkpoints. If something breaks in production, you can revert the release merge in Git. Your deployment system can then build or deploy the resulting state, while database rollback remains a separate concern.",[558,559,561],"h4",{"id":560},"the-shell-script-to-run-it","The Shell Script to Run It",[261,563,567],{"className":564,"code":565,"language":566,"meta":267,"style":267},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# 1. Merge feature into develop\ngit checkout develop\ngit pull origin develop\ngit merge --no-ff feat/billing -m \"feat: integrate billing\"\ngit push origin develop\n\n# 2. Promote develop to staging (Triggers preview & staging DB migration)\ngit checkout staging\ngit pull origin staging\ngit merge --no-ff develop -m \"release(staging): promote develop to staging\"\ngit push origin staging\n\n# ---> PAUSE HERE: Test your preview deployment and verify migrations \u003C---\n\n# 3. Promote staging to production (Triggers prod deploy & prod DB migration)\ngit checkout main\ngit pull origin main\ngit merge --no-ff staging -m \"release(prod): promote staging to main\"\ngit tag -a v1.0.0 -m \"Release v1.0.0\"\ngit push origin main v1.0.0\n\n# 4. Jump back to your daily driver\ngit checkout develop\n","bash",[168,568,569,578,592,605,632,644,651,657,667,678,699,710,715,721,726,732,742,753,774,797,812,817,823],{"__ignoreMap":267},[570,571,574],"span",{"class":572,"line":573},"line",1,[570,575,577],{"class":576},"sHwdD","# 1. Merge feature into develop\n",[570,579,581,585,589],{"class":572,"line":580},2,[570,582,584],{"class":583},"sBMFI","git",[570,586,588],{"class":587},"sfazB"," checkout",[570,590,591],{"class":587}," develop\n",[570,593,595,597,600,603],{"class":572,"line":594},3,[570,596,584],{"class":583},[570,598,599],{"class":587}," pull",[570,601,602],{"class":587}," origin",[570,604,591],{"class":587},[570,606,608,610,613,616,619,622,626,629],{"class":572,"line":607},4,[570,609,584],{"class":583},[570,611,612],{"class":587}," merge",[570,614,615],{"class":587}," --no-ff",[570,617,618],{"class":587}," feat/billing",[570,620,621],{"class":587}," -m",[570,623,625],{"class":624},"sMK4o"," \"",[570,627,628],{"class":587},"feat: integrate billing",[570,630,631],{"class":624},"\"\n",[570,633,635,637,640,642],{"class":572,"line":634},5,[570,636,584],{"class":583},[570,638,639],{"class":587}," push",[570,641,602],{"class":587},[570,643,591],{"class":587},[570,645,647],{"class":572,"line":646},6,[570,648,650],{"emptyLinePlaceholder":649},true,"\n",[570,652,654],{"class":572,"line":653},7,[570,655,656],{"class":576},"# 2. Promote develop to staging (Triggers preview & staging DB migration)\n",[570,658,660,662,664],{"class":572,"line":659},8,[570,661,584],{"class":583},[570,663,588],{"class":587},[570,665,666],{"class":587}," staging\n",[570,668,670,672,674,676],{"class":572,"line":669},9,[570,671,584],{"class":583},[570,673,599],{"class":587},[570,675,602],{"class":587},[570,677,666],{"class":587},[570,679,681,683,685,687,690,692,694,697],{"class":572,"line":680},10,[570,682,584],{"class":583},[570,684,612],{"class":587},[570,686,615],{"class":587},[570,688,689],{"class":587}," develop",[570,691,621],{"class":587},[570,693,625],{"class":624},[570,695,696],{"class":587},"release(staging): promote develop to staging",[570,698,631],{"class":624},[570,700,702,704,706,708],{"class":572,"line":701},11,[570,703,584],{"class":583},[570,705,639],{"class":587},[570,707,602],{"class":587},[570,709,666],{"class":587},[570,711,713],{"class":572,"line":712},12,[570,714,650],{"emptyLinePlaceholder":649},[570,716,718],{"class":572,"line":717},13,[570,719,720],{"class":576},"# ---> PAUSE HERE: Test your preview deployment and verify migrations \u003C---\n",[570,722,724],{"class":572,"line":723},14,[570,725,650],{"emptyLinePlaceholder":649},[570,727,729],{"class":572,"line":728},15,[570,730,731],{"class":576},"# 3. Promote staging to production (Triggers prod deploy & prod DB migration)\n",[570,733,735,737,739],{"class":572,"line":734},16,[570,736,584],{"class":583},[570,738,588],{"class":587},[570,740,741],{"class":587}," main\n",[570,743,745,747,749,751],{"class":572,"line":744},17,[570,746,584],{"class":583},[570,748,599],{"class":587},[570,750,602],{"class":587},[570,752,741],{"class":587},[570,754,756,758,760,762,765,767,769,772],{"class":572,"line":755},18,[570,757,584],{"class":583},[570,759,612],{"class":587},[570,761,615],{"class":587},[570,763,764],{"class":587}," staging",[570,766,621],{"class":587},[570,768,625],{"class":624},[570,770,771],{"class":587},"release(prod): promote staging to main",[570,773,631],{"class":624},[570,775,777,779,782,785,788,790,792,795],{"class":572,"line":776},19,[570,778,584],{"class":583},[570,780,781],{"class":587}," tag",[570,783,784],{"class":587}," -a",[570,786,787],{"class":587}," v1.0.0",[570,789,621],{"class":587},[570,791,625],{"class":624},[570,793,794],{"class":587},"Release v1.0.0",[570,796,631],{"class":624},[570,798,800,802,804,806,809],{"class":572,"line":799},20,[570,801,584],{"class":583},[570,803,639],{"class":587},[570,805,602],{"class":587},[570,807,808],{"class":587}," main",[570,810,811],{"class":587}," v1.0.0\n",[570,813,815],{"class":572,"line":814},21,[570,816,650],{"emptyLinePlaceholder":649},[570,818,820],{"class":572,"line":819},22,[570,821,822],{"class":576},"# 4. Jump back to your daily driver\n",[570,824,826,828,830],{"class":572,"line":825},23,[570,827,584],{"class":583},[570,829,588],{"class":587},[570,831,591],{"class":587},[164,833,834,837,838,840],{},[224,835,836],{},"The golden rule here:"," Once a migration lands on staging, it is set in stone. If it is broken, you do not rewrite it or rebase it. You write a new fix on ",[168,839,177],{}," and push that forward.",[200,842],{},[342,844,846],{"id":845},"option-b-trunk-based-development-with-database-branching","Option B: Trunk-Based Development with Database Branching",[164,848,849,851],{},[335,850,538],{}," You want to move fast, eliminate branch management overhead, and your stack supports database branching (like Neon, Supabase branches, or isolated Cloudflare D1 databases).",[164,853,854,855,291,857,859,860,406],{},"In this setup, you eliminate permanent ",[168,856,181],{},[168,858,177],{}," branches entirely. You maintain short-lived feature branches off ",[224,861,862],{},[168,863,185],{},[282,865],{":config":284,"code":866},"gitGraph%0A%20%20%20commit%20id%3A%20%22v1.0.0%22%0A%20%20%20branch%20feat%2Fauth%0A%20%20%20commit%20id%3A%20%22auth%22%0A%20%20%20commit%20id%3A%20%22auth%20tests%22%0A%20%20%20checkout%20main%0A%20%20%20merge%20feat%2Fauth%20id%3A%20%22merge%20auth%22%0A%0A%20%20%20branch%20feat%2Fcheckout%0A%20%20%20commit%20id%3A%20%22checkout%22%0A%20%20%20commit%20id%3A%20%22checkout%20tests%22%0A%20%20%20checkout%20main%0A%20%20%20merge%20feat%2Fcheckout%20id%3A%20%22merge%20checkout%22%0A%0A%20%20%20branch%20feat%2Fnotifications%0A%20%20%20commit%20id%3A%20%22notifications%22%0A%20%20%20checkout%20main%0A%20%20%20merge%20feat%2Fnotifications%20id%3A%20%22merge%20notifications%22",[218,868,869,878,884,890],{},[221,870,871,877],{},[224,872,873,874,876],{},"Branch off ",[168,875,185],{},":"," Work on short-lived feature branches.",[221,879,880,883],{},[224,881,882],{},"Auto-spin up a database branch:"," When you open a Pull Request, your CI spins up a fresh, isolated database instance cloned from production schema/data.",[221,885,886,889],{},[224,887,888],{},"Test in total isolation:"," The preview URL talks to that temporary database branch. If your migration is broken, close the PR and the temporary database gets destroyed automatically.",[221,891,892,895,896,898],{},[224,893,894],{},"Build & promote artifact:"," Once approved, merge into ",[168,897,185],{}," and deploy the verified build artifact to production.",[164,900,901],{},"No staging traffic jams, no desynchronized branch state, and zero shared database pollution.",[200,903],{},[203,905,907],{"id":906},"_6-summary-which-strategy-fits-your-stack","6. Summary: Which Strategy Fits Your Stack?",[909,910,911,930],"table",{},[912,913,914],"thead",{},[915,916,917,921,924,927],"tr",{},[918,919,920],"th",{},"Feature",[918,922,923],{},"The Rebase Anti-Pattern",[918,925,926],{},"Forward-Only Promotion (Option A)",[918,928,929],{},"Trunk-Based + DB Branching (Option B)",[931,932,933,963,982,1001,1019,1035,1051],"tbody",{},[915,934,935,941,950,958],{},[936,937,938],"td",{},[224,939,940],{},"Branches",[936,942,943,178,945,178,947,949],{},[168,944,185],{},[168,946,181],{},[168,948,177],{}," (rebased)",[936,951,952,473,954,473,956],{},[168,953,177],{},[168,955,181],{},[168,957,185],{},[936,959,960,962],{},[168,961,185],{}," + short PR branches",[915,964,965,970,973,976],{},[936,966,967],{},[224,968,969],{},"History Style",[936,971,972],{},"Fake linear (broken hashes)",[936,974,975],{},"True DAG with clear merge points",[936,977,978,979,981],{},"Linear on ",[168,980,185],{}," (Squash & Merge)",[915,983,984,989,992,995],{},[936,985,986],{},[224,987,988],{},"Code vs Env State",[936,990,991],{},"Confuses git pointer with env state",[936,993,994],{},"Explicit release pointer",[936,996,997,998,1000],{},"Single source of truth (",[168,999,185],{},")",[915,1002,1003,1008,1011,1014],{},[936,1004,1005],{},[224,1006,1007],{},"Deployment Model",[936,1009,1010],{},"Rebuilds source on every branch",[936,1012,1013],{},"Branch promotion",[936,1015,1016],{},[224,1017,1018],{},"Artifact promotion (Build once, deploy anywhere)",[915,1020,1021,1026,1029,1032],{},[936,1022,1023],{},[224,1024,1025],{},"Database State",[936,1027,1028],{},"High risk of migration drift",[936,1030,1031],{},"Safe (forward-only migrations)",[936,1033,1034],{},"Low shared-state risk (isolated DB per PR)",[915,1036,1037,1042,1045,1048],{},[936,1038,1039],{},[224,1040,1041],{},"External Webhooks",[936,1043,1044],{},"Hard to manage",[936,1046,1047],{},"Easy (static staging URL)",[936,1049,1050],{},"Requires mockers or tunnel scripts",[915,1052,1053,1058,1061,1064],{},[936,1054,1055],{},[224,1056,1057],{},"Best For",[936,1059,1060],{},"Anti-pattern",[936,1062,1063],{},"Monoliths, stateful apps, small teams",[936,1065,1066],{},"Teams with strong preview infrastructure and database branching",[200,1068],{},[164,1070,1071],{},"A straight line in your Git graph looks clean in screenshots, but branch rebase hacks often mask underlying deployment state problems.",[164,1073,1074,1075,1078],{},"Remember the core distinction: ",[224,1076,1077],{},"Git branches track code state, deployment pipelines manage environment state, and CD systems promote build artifacts."," Aligning your workflow around those three boundaries will give you clean, reliable releases without the staging chaos.",[1080,1081,1082],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":267,"searchDepth":580,"depth":580,"links":1084},[1085,1086,1087,1092,1093,1097],{"id":205,"depth":580,"text":206},{"id":308,"depth":580,"text":309},{"id":329,"depth":580,"text":330,"children":1088},[1089,1090,1091],{"id":344,"depth":594,"text":345},{"id":395,"depth":594,"text":396},{"id":416,"depth":594,"text":417},{"id":466,"depth":580,"text":467},{"id":525,"depth":580,"text":526,"children":1094},[1095,1096],{"id":532,"depth":594,"text":533},{"id":845,"depth":594,"text":846},{"id":906,"depth":580,"text":907},"2026-09-29","Exploring why using Git branches as proxies for deployment environments fails modern CI/CD, how persistent state and build artifacts play into it, and two practical alternatives.","md",null,{"type":1103},"Git",{"subtitle":1105},"Git Branch Theater: Why Your Staging Branch Isn't Doing What You Think",{"title":38,"description":1099},{"src":1108},"https://placehold.co/800x400/0f172a/3b82f6?text=Git+Branching+2026","hqbegQuf-KkgOiH5xXFNCnrLqygIHmbxUCfTGLewDTk",[1111,1117],{"title":34,"path":35,"stem":36,"description":1112,"imageComponent":1113,"seoImage":1115,"children":-1},"How Vite PWA intercepted navigation requests in production and caused redirect loops.",{"type":1114},"Blooper",{"src":1116},"https://placehold.co/800x400/0f172a/3b82f6?text=PWA+Redirecting+SPA+Routes",{"title":42,"path":43,"stem":44,"description":1118,"imageComponent":1119,"seoImage":1121,"children":-1},"Where to host your side project without getting a massive surprise bill in 2026. A breakdown of static and container platforms.",{"type":1120},"Cloud",{"src":1122},"https://placehold.co/800x400/0f172a/3b82f6?text=Avoiding+Cloud+Billing+Traps",1790708387320]