Learn GitLab CI/CD - Environments, Deployments & Manual Approval Gates
Episode 14 of 21

Learn GitLab CI/CD - Environments, Deployments & Manual Approval Gates

Deploying to production isn't just running a script — it needs a trail, control, and human approval. This episode covers declaring environments, deployment history in the GitLab UI, manual approval gates with when manual, protected environments, and Review Apps that live and die automatically with merge requests.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In the previous episode 13 we covered how pipelines authenticate to the cloud with OIDC without storing permanent credentials. Now the question shifts: where are those temporary credentials used to deploy? That's where the environment concept comes in.

Imagine monitoring a building management team without a clear key system: anyone can enter, anytime, with no records. When there's a problem on floor 3, nobody knows when the last change was made or by whom. Software deployment faces the same problem. Without a tracking system, "who deployed what version where" is a mystery — and when production is down, that mystery is very expensive.

GitLab solves this with the Environment and Deployment concepts: every target receiving build results (staging, production, review app) is declared explicitly, and every deployment execution is recorded as a traceable deployment entry in the UI. This episode covers declaring environments, manual approval gates for production, protected environments, and Review Apps — dynamic environments that live and die with the merge request lifecycle.

Main Discussion

The Environment Concept: Declared Deployment Targets

An Environment is a target where the application is deployed — it could be staging, production, or a per-branch environment. When a job declares environment, GitLab links the pipeline result to that environment and records it as a new deployment.

Declaring an environment with a URL
deploy-staging:
  stage: deploy
  environment:
    name: staging
    url: https://staging.myapp.com
  script:
    - echo "Deploy ke staging"

The two most important attributes:

  1. name — the environment identity. Common convention: staging, production, or review/<branch-name>.
  2. url — the application address. This value makes GitLab display a button to open the environment directly from the pipeline and merge request.

Once this job succeeds, a deployment is recorded — GitLab knows which version (based on commit SHA) is currently active in that environment.

Tracking Deployment History in the GitLab UI

All recorded deployments can be traced via the Operate → Environments (or Deployments) menu in the project sidebar. This page shows:

  • A list of environments with their current status — whether the last deployment succeeded, is running, or failed.
  • Deployment history per environment: when, at which commit, by which pipeline, and the result.
  • A rollback button to return an environment to a previous deployment.

Rollback isn't just a convenience feature — it's one of the strongest reasons to always declare environment on every deployment job. When production has a problem, the team simply clicks rollback to the last healthy deployment instead of rerunning the pipeline from scratch.

Manual Approval Gates: when: manual

Deploying to production ideally isn't an automatic decision. GitLab provides an approval gate with the when: manual keyword: the job doesn't run until a human clicks the play button in the UI.

Production deployed only after manual approval
deploy-production:
  stage: deploy
  environment:
    name: production
    url: https://myapp.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
  script:
    - echo "Deploy ke produksi"

The rules + when: manual combination is the recommended modern pattern: the job only appears in pipelines on the main branch, and only runs when a human clicks it. A manual job holds the pipeline at that stage — the next stage doesn't run until this job is executed or skipped.

Warning

when: manual without extra protection can be abused by anyone who can trigger a pipeline. A manual approval gate alone only delays execution, it doesn't restrict who may execute. To restrict execution to specific people, combine it with a protected environment (next).

Protected Environments: Restricting Who Can Deploy

Protected environments add an access-control layer to environments. When enabled, GitLab only allows users with specific roles — for example Maintainers or Release Managers — to execute deployment jobs to that environment. Environments can also be restricted to specific branches, for example main only.

Protected environment for production
deploy-production:
  stage: deploy
  environment:
    name: production
    url: https://myapp.com
    protected: true
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual

The who can deploy configuration is done in Settings → CI/CD → Protected environments: choose the environment (e.g. production), then set which roles may deploy and which may approve. With this, the approval gate isn't just a button — it can only be clicked by authorized people.

Dynamic Environments & Review Apps

One of the most productive features in GitLab is Review Apps: every merge request automatically gets a temporary environment (spin-up) for the feature version of the app, so reviewers can try the change for real before approving the MR.

Review app with auto-stop
deploy-review:
  stage: deploy
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_COMMIT_REF_SLUG.review.myapp.com
    on_stop: stop-review
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script:
    - echo "Deploy review app untuk branch ini"
 
stop-review:
  stage: deploy
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual
  script:
    - echo "Hentikan review app"

Notice the patterns:

  • The environment name is built dynamically with $CI_COMMIT_REF_SLUG — a slug of the branch name, so every branch gets a unique environment.
  • The deploy-review job uses on_stop: stop-review — pointing to the job tasked with stopping the environment.
  • The stop-review job uses action: stop, which marks the environment as stopped when run.

Tip

A stopped environment doesn't automatically delete its cloud resources — that's the job of the stop-review job's script. Best practice is bundling the teardown action (e.g. deleting a VM, pod, or domain) in the job with action: stop, so finished MRs don't leave idle environments racking up costs.

Common Environment Management Mistakes

MistakeSymptomSolution
Not declaring environmentNo deployment history, no rollbackAlways add environment to deploy jobs
when: manual without a protected environmentAnyone can execute productionCombine with a protected environment
Wrong or empty environment URL"view app" button doesn't appearFill url with the correct app address
Review apps never stoppedCloud costs balloonUse on_stop + an action: stop job
Rollback used without verificationOld bugs return to productionVerify the target commit before rolling back
Manual environment without rulesJob waits for a click on every pipelineRestrict with branch-specific rules: if

Closing

In this episode we covered the environment concept as declared deployment targets with environment: name and url; tracking deployment history through the Deployments menu and the rollback feature in the GitLab UI; manual approval gates with when: manual that hold deployments until a human clicks; protected environments restricting execution to specific roles; and Review Apps with on_stop and action: stop for dynamic environments that live and die with merge requests.

The core of this episode: an untracked deployment is a risk; a tracked deployment is an asset. With managed environments, teams know when, by whom, and where every application version was shipped — and can pull it back at any time.

In the next episode 15 we close PHASE 6 with the most hands-on work: Continuous Deployment to Linux Servers via SSH and Ansible — setting up SSH keys, ssh-agent and ssh-keyscan in the pipeline, syncing files with rsync, and automating full deployment with Ansible playbooks. See you there!

Learn GitLab CI/CD - Environments, Deployments & Manual Approval Gates | Learn GitLab CI/CD