Edward Thomson

This is day 20 of my GitHub Actions Advent Calendar. If you want to see the whole list of tips as they're published, see the index.

It's hard to underestimate the importance of containers in DevOps practices. Often, you'll deploy a container to production – so it's natural to start using containers to do your local development. And to manage dependencies. We looked at how we could leverage this to do our builds inside containers. But we can also leverage running containers as part of our build and test workflow, using container services.

You'll often want to run some integration tests that talk to other services, often a database. And you could script this, by writing a docker run command to pull the container down, start it, and map the necessary ports… but that's annoying in the best case. And if you're building inside a container then running docker yourself becomes a lot trickier.

Using a container service lets the GitHub Actions infrastructure take care of the execution for you. You can just specify the container and any ports to map and it will start the service containers at the beginning of your job and make it available to the steps within the job.

services:
  redis:
    image: 'redis:latest'
    ports:
    - 6379/tcp

Will start the redis:latest container and map port 6379 in the container to a port on the virtual machine runner. This is the moral equivalent of running docker run redis:latest -p 6379/tcp, and just like if you'd run that, the port that is mapped on the local runner is not deterministic. GitHub Actions makes this information available in the job.services context.

You can look at ${{ job.services.redis.ports[6379] }} to identify the local port number. (Just like running docker run, you can also specify the container port and the local port, eg 6379:6379 to map the container port 6379 to port 6379 locally.)

Putting this into a workflow, if I have a Node script that talks to Redis, connecting to the Redis host specified by the REDIS_HOST environment variable on REDIS_PORT, then I can create a workflow that starts my Redis container and then runs my Node script.

You can use service containers to start services like Redis, PostgreSQL or MySQL – or even Selenium. The service container execution makes executing and interacting with these containers in your workflow much easier.