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:

  • DEBUG
  • DB_HOST
  • STRIPE_KEY
  • ALLOWED_HOSTS

โš ๏ธ Note: the original source only listed these variable names โ€” it didn't include the per-environment values (e.g. what DEBUG is 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 dev branch and a main/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 main branch. That's your single source of truth.
  • โœ… Feature branches are disposable. Branch, code, PR, merge, delete.
  • โœ… Staging = server, not branch. Deploy main to 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.