Working on CI/CD pipelines


During my first experience working on CI/CD pipelines I had to wait a lot. Some jobs took forever to finish and silly me just need to verify or test one job adjustment and off-course the job would fail due to misconfiguration or other stupid mistakes.

Well that meant I had a lot of time to figure out how to be productive while working on new pipeline jobs or maintenance.

Introduction

At first, I tried to comment out all unnecessary jobs or stages in the pipeline. That saved me some time during testing, but it came with a cost: I had to manually clean everything up afterwards. A bit of a hassle, but I got things done — a little faster.

Still, I was spending too much time waiting. So, more time to tinker!

Faster feedback loops

To speed things up, I realized I needed faster feedback. Running jobs locally seemed like the way to go.

Installing a runner or agent on my machine was an option, but felt overly complex for most of my use cases. Luckily, since my jobs mostly consisted of running Bash or Python scripts, I could easily execute them locally.

Here’s what I did:

my_hello_world_job:
	variable:
		MY_NAME:Sjors
	script: scripts/say_hello.sh

I moved my scripts into a scripts/ directory. Each script contains metadata like a short description and a list of requirements. I also added checks for environment variables, so it’s obvious when something is missing:

#!/bin/bash
# This is a simple script which outputs: hello to MY_NAME

if [[ -z "${MY_NAME}" ]]; then  
  echo "Environment variable MY_NAME is not set!"
  exit 1
fi

echo "Hello $MY_NAME!"

And boom! It prints Hello Sjors! — no need to commit and push to the pipeline just to test this.

Matching Environments

The only downside: you need to have the same dependencies locally as the runner.

This can be solved by using container images that include all required tools and versions. That way, your local environment mirrors the CI runner, ensuring consistent results.

The End (Or Beginning?)

This experience taught me a valuable lesson: keep your scripts external. Don’t write all your logic directly in your gitlab-ci.yml file — it becomes unreproducible locally and wastes both your time and your sanity.

For example, this might look simple:

my_hello_world_job:
	variable:
		MY_NAME:Sjors
	script: |
		echo "Hello $MY_NAME"

But it’s much harder to test locally than this:

./scripts/say_hello.sh

Trust me — your future self will thank you.

Bonus Tip

Want even more reproducibility? Wrap your script execution in a container using your CI image:

podman run --rm -e MY_NAME=Sjors -v $(pwd):/app my-ci-image bash /app/scripts/say_hello.sh

That way, you’re testing in the exact same environment as your CI/CD pipeline.

Note: bash is not installed by default in a Alpine image!

Conclusion

CI/CD pipelines don’t need to slow you down. A few simple tweaks — like extracting scripts, validating variables, and using containers — can drastically improve your feedback loop and productivity. Happy coding! 🚀