Staging Is Not Dev: Understanding Git Workflows and Environments
August 23, 2026 ยท Ahmad M. Waddah ยท 31 reads
๐ Staging Is Not Dev: Understanding Git Workflows and Environments
One of the biggest confusions for junior developers: where do I code, where do I test, and how does Git fit in?
๐ค The Problem
Beginners think there are two places: their laptop and "the server."
Wrong. There are three.
๐ฅ๏ธ The Three Environments
| Icon | Environment | Where | Purpose | Data | Who |
|---|---|---|---|---|---|
| ๐ป | Development | Your laptop | Write code, run manage.py runserver |
Fake data, sqlite |
You |
| ๐งช | Staging | Server (AWS, Render) | Mirror of prod, catch real-world issues | Copy of prod (anonymized) | QA / Team |
| ๐ | Production | Server (Hetzner, AWS) | Real users, real money | Real data | Everyone |
๐ Key Insight: Staging and Production run the same code. Only config changes.
๐ The Flow (GitHub Flow)
feature/login โโโบ PR + Review โโโบ main โโโบ Deploy โโโบ ๐งช Staging
โ
โ
Pass?
โ
โโโโ Yes โโโโโดโโโ No โโโโ
โผ โผ
๐ Prod Fix on main
โ
No develop branch.
โ
No staging branch.
โ
Just main.
Feature branches are short-lived: Branch โ Code โ PR โ Merge โ Delete. Done.
โ Why No Staging Branch?
Because Staging is a server, not a branch.
Deploy main to both environments. Only environment variables differ:
# settings.py (simplified)
import os
DEBUG = os.getenv("DEBUG", "False") == "True"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.getenv("DB_NAME"),
"HOST": os.getenv("DB_HOST"),
"PASSWORD": os.getenv("DB_PASSWORD"),
}
}
STRIPE_KEY = os.getenv("STRIPE_SECRET_KEY")
Same code. Different .env file. That's it.
Variables that typically change between environments:
DEBUGDB_HOSTSTRIPE_KEYALLOWED_HOSTS
โ ๏ธ Note: the original source only listed these variable names โ it didn't include the per-environment values (e.g. what
DEBUGis set to in dev vs. staging vs. prod). Let me know the values if you'd like a full comparison table.
โ๏ธ The YAML Pattern (Agnostic)
Generic CI/CD pattern โ adapt to GitHub Actions, GitLab CI, or whatever:
# Generic CI/CD Pattern (not vendor-locked)
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: |
docker build -t myapp:${{ github.sha }} .
- name: Deploy Staging
env:
SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
HOST: staging.example.com
run: |
echo "Deploy to staging..."
- name: Smoke Test
run: |
curl -f https://staging.example.com/health || exit 1
- name: Deploy Production
if: success()
env:
SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
HOST: prod.example.com
run: |
echo "Deploy to prod..."
๐ Key Point: Build once, deploy twice. Same artifact, different config.
๐ฉ Feature Flags > Long-Lived Branches
Working on a feature for two weeks but keeping main deployable? Use a flag, not a branch:
# Simple Django (no library needed)
from django.conf import settings
def is_feature_enabled(request, feature_name):
"""Check if a feature flag is enabled for this user."""
flags = getattr(settings, "FEATURE_FLAGS", {})
return flags.get(feature_name, False)
# settings/production.py
FEATURE_FLAGS = {
"new_dashboard": False, # Flip to True when ready
"beta_search": True,
}
โ No branch needed. โ No merge conflict risk. โ Just flip a switch.
โ ๏ธ Common Mistakes to Avoid
- โ Having both a
devbranch and amain/prod branch - โ Treating "staging" as a branch instead of a deployed environment
- โ Hardcoding environment-specific logic directly in code
- โ Letting feature branches live for weeks before merging
โ ๏ธ Note: the original source's mistakes table was missing its second column (presumably "why it's a problem" or "what to do instead"). I've listed the mistakes above โ happy to add explanations for each if useful.
๐ TL;DR Checklist
- โ
One
mainbranch. That's your single source of truth. - โ Feature branches are disposable. Branch, code, PR, merge, delete.
- โ
Staging = server, not branch. Deploy
mainto staging with staging config. - โ
Same code, different
.env. Never hardcode environment-specific logic. - โ Use feature flags for work-in-progress. Not long-lived branches.
- โ Build once, deploy twice. Same artifact, different host.