GitLab CI/CD isn’t just about automating builds; it’s a declarative system for defining and executing complex workflows across your entire software lifecycle, from commit to production.
Let’s see this in action with a simple example. Imagine you have a Node.js app. Your .gitlab-ci.yml file might look like this:
stages:
- build
- test
- deploy
variables:
NODE_VERSION: "18.17.0"
build_job:
stage: build
image: node:$NODE_VERSION
script:
- npm install
- npm run build
artifacts:
paths:
- dist/
test_job:
stage: test
image: node:$NODE_VERSION
script:
- npm test
deploy_staging:
stage: deploy
image: alpine:latest
environment:
name: staging
url: https://staging.example.com
script:
- echo "Deploying to staging..."
# In a real scenario, you'd use ssh, rsync, or a cloud provider CLI here
- echo "Deployment to staging complete."
only:
- main
deploy_production:
stage: deploy
image: alpine:latest
environment:
name: production
url: https://example.com
script:
- echo "Deploying to production..."
# In a real scenario, you'd use ssh, rsync, or a cloud provider CLI here
- echo "Deployment to production complete."
when: manual
only:
- main
When you push a commit to the main branch, GitLab CI kicks off a pipeline. The build_job runs first. It pulls a Docker image tagged node:18.17.0, executes npm install and npm run build, and then saves the contents of the dist/ directory as an artifact. These artifacts are crucial; they’re the output of one job that can be used by subsequent jobs, ensuring consistency.
Next, the test_job runs, also using the node:18.17.0 image. It executes npm test. If this job fails, the pipeline stops, preventing broken code from progressing. If it succeeds, the pipeline continues.
Finally, the deploy_staging job runs. Notice it uses a different image (alpine:latest) because it doesn’t need Node.js to deploy; it just needs basic shell commands. It’s configured to deploy to an environment named staging and provides a url for that environment. This environment keyword is key – it tells GitLab where your application is deployed, enabling features like environment history and rollback. This job runs automatically for commits on the main branch.
The deploy_production job is similar but has when: manual. This means it won’t run automatically. Someone has to click a "play" button in the GitLab UI to trigger it, providing a safety net for production deployments.
Under the hood, GitLab CI relies on Runners. These are the agents that actually execute your jobs. You can have shared runners managed by GitLab.com, or you can set up your own specific runners on your infrastructure. Each runner has tags, and you can specify which tags a job should use in your .gitlab-ci.yml. This allows you to control where jobs run – for example, a job needing GPU access would only be picked up by a runner tagged with gpu. The image directive in your .gitlab-ci.yml tells the runner which Docker image to pull and use for the job’s execution environment. This isolation is powerful; each job gets a clean slate, defined precisely by the image and your script.
The stages array defines the order in which jobs are executed. Jobs within the same stage run in parallel if you have enough available runners with matching tags. Jobs in later stages only start after all jobs in the preceding stage have successfully completed. This structured approach ensures that your build, test, and deploy processes are executed in a predictable and reliable sequence.
GitLab CI/CD’s strength lies in its declarative nature. You define what you want to happen (stages, jobs, scripts, artifacts, environments), and GitLab CI figures out how to make it happen by orchestrating runners and managing the workflow. This abstraction simplifies complex deployment pipelines, making them understandable and maintainable directly within your Git repository.
The only and except keywords, along with rules, allow for fine-grained control over when specific jobs run. For instance, you might only want a deployment job to run on tags, or only when specific files have changed, preventing unnecessary runs and saving CI minutes.
The true power of the environment keyword becomes apparent when you have multiple deployments. GitLab tracks which commit was deployed to which environment, offering a clear history and the ability to redeploy previous versions or even trigger rollbacks directly from the UI, tying your deployment process directly to your version control history.
This system provides a robust framework for continuous integration and continuous delivery, tightly integrated with your code repository. The next logical step is to explore advanced artifact management and caching strategies to optimize your pipeline’s speed and resource usage.