Example .gitlab-ci.yaml with descriptive comments
#automation #GitLab #CI/CD #DevOps
stages: # Stages in order of execution
- build # (Jobs in the same stage can run in parallel)
- test
- deploy
default: # Defaults that apply to all jobs when not defined
image: node
.standard-rules: # Make a hidden job to hold the common rules
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
# Run for all changes to a merge request's source branch
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Run for all changes to the default branch
.test-cache:
cache: # Cache modules in between jobs
key:
files: # Cache gets rebuilt if file changes
- package-lock.json # npm install --save-dev htmlhint markdownlint-cli2
paths:
- .npm/
before_script:
- npm ci --cache .npm --prefer-offline
build-job:
extends: # Reuse the configuration in `.standard-rules` here
- .standard-rules
stage: build # Set this job to run in the `build` stage
tags:
- kubernetes # Runner selection "kubernetes"
script:
- npm install
- npm run build
artifacts: # Save artifacts so later jobs can retrieve them with "dependencies:"
paths:
- "build/"
lint-markdown:
stage: test
extends:
- .standard-rules
- .test-cache
dependencies: [] # Don't fetch any artifacts
script:
- npx markdownlint-cli2 "blog/**/*.md" "docs/**/*.md"
allow_failure: true # This job fails right now, but don't let it stop the pipeline.
test-html:
stage: test
extends:
- .standard-rules # Reuse the configuration in `.standard-rules` here
- .test-cache
dependencies:
- build-job # Only fetch artifacts from `build-job`
script:
- npx htmlhint build/
integration-test: # Add postgres DB instance
stage: test
image: alpine:3.23.2
extends:
- .standard-rules
services:
- name: postgres:17
alias: db
variables:
POSTGRES_DB: custom_db
POSTGRES_USER: custom_user
POSTGRES_PASSWORD: custom_pass
script:
- cat /etc/hosts
- apk add postgresql17-client
- export PGPASSWORD=$POSTGRES_PASSWORD
- psql -h "postgres" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT 'OK' AS status;"
pages:
stage: deploy
image: busybox # Override the default `image` value with `busybox`
dependencies:
- build-job
script:
- mv build/ public/
environment: # Register this job as a deployment
name: production # Name shown in the Environments UI
url: https://$CI_PROJECT_NAMESPACE.gitlab.io/$CI_PROJECT_NAME
# "View deployment" - Link to the live site
artifacts:
paths:
- "public/"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Build a Docker image and push it to the project's container registry
# Uses the Dockerfile in the repo root. The image is tagged with the branch name,
# so each branch publishes its own image (e.g. registry.example.com/group/proj:main).
build-image:
stage: build
image: docker:24.0.5-cli # job runs in a container that has the docker CLI
services:
- docker:24.0.5-dind # sidecar daemon - the CLI talks to this
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # only build images on main
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
- docker build -t $IMAGE_TAG . # "." = repo root, finds Dockerfile
- docker push $IMAGE_TAG
# upload a build artifact to the generic package registry on git tag
# Versioned by the tag (e.g. v1.2.0), so each release has its own download URL.
publish:
stage: release
image: curlimages/curl:latest
dependencies:
- build-job # pull build/ artifacts from build-job
rules:
- if: $CI_COMMIT_TAG # only run when a git tag is pushed
script:
- |
curl --location --fail --user "gitlab-ci-token:${CI_JOB_TOKEN}" \
--upload-file build/app.tar.gz \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/my_package/${CI_COMMIT_TAG}/app.tar.gz"
# Example Push-Based Deployment to k8s
deploy_to_k8s:
stage: deploy
image: alpine/kubectl:1.34
script:
- echo "$KUBE_CA_PEM" | base64 -d > ca.crt
- export KUBECONFIG=$(pwd)/kubeconfig
- cat "$KUBE_CONFIG_SECRET_FILE" > "$KUBECONFIG"
- |
yq eval ".spec.template.spec.containers[0].image = \"${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}\"" \
-i k8s/deployment.yaml
- kubectl apply -f k8s/deployment.yaml
only:
- main # run only on the production branch
environment:
name: production
url: https://demo.example.com