GitHub Environments are a powerful way to manage deployments by defining distinct stages for your application, like staging or production, and controlling who can deploy to them and when.

Here’s a real-world example of a GitHub Actions workflow that uses environments and approval gates:

name: Deploy to Production

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://your-app.com # This URL will be displayed on the deployment status

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Deploy to production
        uses: actions/deploy-pages@v2 # Example deployment action
        with:
          # Deployment credentials or configuration would go here
          # For example, a service principal or API key

          production-token: ${{ secrets.PRODUCTION_DEPLOY_TOKEN }}


          deployment-id: ${{ github.run_id }}

In this workflow, the deploy job is explicitly tied to the production environment. When this workflow runs, GitHub will automatically check if the production environment has any configured protection rules.

The core problem environments solve is the lack of granular control over deployments. Without them, a push to main might directly trigger a production deployment, bypassing any necessary review or manual approval. Environments provide a structured way to introduce these controls.

Internally, GitHub Environments act as a stateful resource. When a job targets an environment, GitHub tracks that environment’s status, associated secrets, and, crucially, its protection rules. These rules are configured within the GitHub repository settings under "Environments."

The primary levers you control are:

  • Required reviewers: You can specify individual users or teams who must approve a deployment before it can proceed. This is the "approval gate."
  • Wait timer: You can set a mandatory waiting period after a workflow is triggered but before deployment can occur. This allows for a cool-down period or a window for last-minute cancellations.
  • Deployment branches: You can restrict which branches are allowed to deploy to a specific environment. For example, only deployments from the main branch might be permitted to the production environment.
  • Environment secrets: You can store secrets (like API keys, passwords, or tokens) that are specific to an environment. These secrets are only accessible to jobs running in that environment, enhancing security.

When a workflow targets an environment with protection rules, GitHub pauses the job at the point of deployment. It then checks the configured rules:

  1. Branch Protection: If the triggering branch isn’t allowed for this environment, the deployment fails immediately.
  2. Wait Timer: If a wait timer is active, the job waits for the specified duration.
  3. Required Reviewers: If required reviewers are set, GitHub displays a prompt on the environment’s page in the repository settings, showing the pending deployment. The specified reviewers receive a notification and can then approve or reject the deployment. If approved, the job resumes. If rejected, it fails.

One of the most powerful, yet often overlooked, aspects of environments is how they integrate with GitHub’s audit logs and deployment history. Every approval, rejection, or deployment to an environment is recorded. This provides a clear, auditable trail of who did what, when, and to which environment, which is invaluable for compliance and incident response. You can see the full history of deployments to production directly on the environment’s page in your repository settings, including the commit SHA, the workflow run, and the status.

The next concept to explore is how to automate the creation and management of these environments using the GitHub API or tools like Terraform, rather than relying solely on manual configuration in the UI.

Want structured learning?

Take the full Github course →