Development Staging Server

Plain-English guides to dev, staging, and production environments

How Should Code Move From Development to Staging With Git?

The core idea: staging tracks a branch

The simplest reliable arrangement is that your staging environment always runs a known point in Git history — usually the tip of one designated branch. Code reaches staging not by someone copying files to a server, but by merging into that branch, after which an automated deploy puts exactly that commit onto the staging server. The commit hash is the single source of truth: at any moment you can answer "what is running on staging?" with a specific, reproducible revision.

The two most common shapes of this workflow:

  • Main-based: feature branches merge into `main`; staging automatically deploys every merge to `main`; production deploys are cut from `main` by tag. This is the lighter-weight option and suits teams that release often.
  • Promotion-based: feature branches merge into a `develop` (or `staging`) branch that deploys to staging; when staging looks healthy, that state is merged or tagged forward into `main`, which deploys to production. This adds a deliberate promotion step at the cost of one more branch to keep in sync.

Either works. What matters is that the mapping from branch to environment is written down and automated, so nobody has to remember it.

The Git mechanics involved

The workflow leans on a small set of commands, all covered in the official Git reference documentation: `branch` and `switch` for creating and moving between lines of work, `merge` for integrating a finished feature, `tag` for marking the exact commits that were released, and `log` for reconstructing what changed between two deployments. Tags deserve particular attention in a staging workflow: an annotated tag on every commit promoted past staging gives you a permanent, human-readable record of releases, and makes rolling staging back as simple as deploying a previous tag.

A habit worth adopting early: the deploy process should record the deployed commit hash somewhere visible — a version endpoint, a footer, a log line. When staging misbehaves, the first question is always "what exactly is running?", and this makes it a five-second answer.

Keep the deploy identical across environments

However code gets from the branch onto the server — a pipeline, a deploy script, a container build — the same mechanism should serve both staging and production, with only environment-specific configuration differing. This is the Twelve-Factor build/release/run separation in practice: one build artifact, combined with each environment's own config, produces each environment's release. If staging is deployed by a script and production by a different, hand-maintained procedure, then your staging step tests the code but never the deployment — and deployment is where a large share of release failures live. The same logic argues for parity between the environments themselves.

Protect the pipeline, not just the server

A branch-driven deploy means that write access to the staging branch is effectively deploy access to the staging environment. NIST's Secure Software Development Framework (SP 800-218) treats protecting code repositories and build pipelines as a first-class security practice, and the implications for a staging workflow are concrete: require review before merging into deployable branches, restrict who can push to them directly, and give the deploy automation its own least-privilege credentials rather than a person's. None of this is heavyweight — most hosted Git platforms expose branch protection as a checkbox — and it closes the gap between "our staging server is locked down" and "anyone who can push can change what it runs."

Where to start

If you currently deploy to staging by hand: pick one branch as staging's source of truth, write the smallest script that deploys its tip, and run that script from automation on every merge. Everything else — tags, promotion branches, protections — layers on incrementally once that loop exists.

Sources