GitLab CI isn’t just about building and testing; it’s a full-fledged deployment engine that lets you push code to different environments and track every single release.

Let’s see it in action. Imagine you have a simple web application. Here’s how your .gitlab-ci.yml might look to deploy to a staging and a production environment:

stages:
  - build
  - deploy

variables:
  APP_NAME: my-awesome-app
  STAGING_SERVER: 192.168.1.100
  PRODUCTION_SERVER: 192.168.1.200
  SSH_PRIVATE_KEY: $SSH_PRIVATE_KEY # GitLab CI/CD variable

build_app:
  stage: build
  script:
    - echo "Building the application..."
    - echo "Application built successfully."
  artifacts:
    paths:
      - build/

deploy_staging:
  stage: deploy
  environment:
    name: staging
    url: http://$STAGING_SERVER
  before_script:
    - apt-get update -yqq
    - apt-get install -yqq openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan $STAGING_SERVER >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    - echo "Deploying to staging environment..."
    - scp -r build/ $APP_NAME@$STAGING_SERVER:/var/www/html/
    - ssh $APP_NAME@$STAGING_SERVER "systemctl restart nginx"
  only:
    - main # Deploy to staging only on commits to the main branch

deploy_production:
  stage: deploy
  environment:
    name: production
    url: http://$PRODUCTION_SERVER
  before_script:
    - apt-get update -yqq
    - apt-get install -yqq openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan $PRODUCTION_SERVER >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    - echo "Deploying to production environment..."
    - scp -r build/ $APP_NAME@$PRODUCTION_SERVER:/var/www/html/
    - ssh $APP_NAME@$PRODUCTION_SERVER "systemctl restart nginx"
  when: manual # Deploy to production only when manually triggered
  only:
    - main # Only allow production deployments from the main branch

This .gitlab-ci.yml defines two deployment jobs, deploy_staging and deploy_production. The build_app job creates an artifact, which is then used by the deployment jobs.

Notice the environment keyword. This is crucial. It tells GitLab CI that this job is deploying to a specific environment. GitLab then uses this information to:

  • Track Releases: On your project’s Deployments page, you’ll see a history of every time code was deployed to an environment, along with the commit and the user who triggered it.
  • Provide Environment URLs: The url field in the environment block links directly to your deployed application.
  • Manage Environment Status: GitLab can mark environments as "available," "stopped," or "updating," giving you a clear overview of your deployment status.

The before_script section handles setting up SSH access, which is a common pattern for deployments. It adds your SSH private key (stored as a CI/CD variable in GitLab) to the agent, creates an SSH directory, and adds the server’s host key to known_hosts to avoid interactive prompts. The script then uses scp to copy the built application files and ssh to restart the web server.

The only and when keywords control when these jobs run. deploy_staging runs automatically on every commit to the main branch. deploy_production, however, is set to when: manual, meaning a user has to explicitly click a button in the GitLab UI to trigger the production deployment, providing a crucial safety net.

The real magic is how GitLab ties these environment definitions to your project’s CI/CD > Deployments section. Each successful deployment to an environment gets recorded there. You can see which commit was deployed, when, by whom, and to which environment. This provides an auditable trail of all your deployments, which is invaluable for understanding your release history and troubleshooting issues.

When you define an environment, GitLab automatically creates a corresponding entry in the Deployments view. If you deploy the same commit multiple times to the same environment, GitLab will show multiple deployment records, each associated with that specific commit. If you then deploy a different commit to that environment, it will appear as a new, distinct deployment. This granular tracking is what makes GitLab CI so powerful for managing releases.

The most surprising thing about GitLab environments is that they aren’t just a label; they are a managed resource within GitLab. This means GitLab can track the state of an environment. For example, if a deployment job fails, the environment can be marked as "unavailable." You can also manually "stop" an environment, which effectively tears down any resources associated with it (though this is less common for simple web deployments and more for ephemeral environments like review apps). This statefulness allows for more sophisticated deployment strategies and better visibility into your infrastructure.

To truly leverage environments, consider using GitLab’s built-in feature for defining environment-specific variables. Instead of putting all your sensitive credentials in the main CI/CD settings, you can go to your project’s Settings > CI/CD > Environments and define variables specific to each environment (e.g., PRODUCTION_API_KEY for the production environment). This prevents accidental deployment of production secrets to staging and keeps your configuration clean.

You’ll next want to explore how to implement blue-green deployments or canary releases to make your production deployments even safer.

Want structured learning?

Take the full Gitlab-ci course →