GitHub Workflows: Overriding A Service's Entrypoint
Lately I've been migrating an ancient Jenkins CI system over to GitHub Actions/Workflows. One of our Node.js package's tests rely on being able to hit local MongoDB and etcd containers.
To get set up for running unit tests within our Node.js package our Jenkins CI would build everything from a docker-compose.yml
that looks about like this:
version: '3'
services:
our_node_package:
build: .
environment:
ETCD: etcd:2379
NODE_ENV: ci
links:
- mongodb
- etcd
mongodb:
image: mongo:4.0
hostname: mongodb
container_name: build_mongodb
command: mongod --nojournal --noprealloc --smallfiles
etcd:
image: microbox/etcd:2.1.1
entrypoint: "etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379"
hostname: etcd
container_name: build_etcd
expose:
- 2379
You can assume there's aDockerfile
that includes Node and all that. Hence thebuild: .
which just builds theDockerfile
in the repositories root directory.
Translating this into a workflow might look something like this:
name: Node CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
mongo-version: [4.0]
services:
mongodb:
image: mongo:${{ matrix.mongo-version }}
ports:
- 27017:27017
etcd:
image: microbox/etcd:2.1.1
ports:
- 2379:2379
options: --hostname etcd --name build_etcd
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org'
- name: Install node modules
run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Run unit tests
run: npm test
env:
NODE_ENV: 'ci'
ETCD: etcd:2379
However, there's one thing missing... The etcd entrypoint
override from our docker-compose.yml
. This bit:
entrypoint: "etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379"
Overriding the Entrypoint
How can we do it? If we look at the documentation, there's no entrypoint
field for a service like there is in a docker compose file. Jobs have it but not services. What services do have, is an options
field. This allows you to add arbitrary docker create options, one of which is --entrypoint
, which allows you to override a container's entrypoint.
But there's a problem.
Because we care about the parameters (--listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379
) and this confusing shit, it ain't gonna work.
Here's the proof Actions doesn't support it (as of this writing).
The Workaround
Since GitHub Workflows are just yaml files defining commands to run on a VM there are a few ways around this.
My suggestion is, in your workflow, just run the friggin' docker-compose.yml
. It'll handle entrypoint overrides properly.
Assuming there's a docker-compose.yml
in the root of your repository Here's what the new workflow might look like:
name: Node CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: docker-compose build --pull --force-rm our_node_package
- name: Test
run: docker-compose run --rm our_node_package test
- name: Cleanup
run: docker-compose down && docker-compose rm --force
This will download images and run the containers just like you want, overridden entrypoint and all.
If you're pulling private Docker images you'll need to add adocker login
step with adocker logout
step at the end. Warning: Make sure you aren't hard coding any secrets into this file.
The Benefits
Using a docker compose file instead of defining the entire build process in a Workflow loosens the coupling between your CI system and GitHub's Workflow platform.
In my case we'll probably be using Docker containers for a long time but who knows how long we'll stick with GitHub for our CI system. A docker compose should work on any platform that runs Docker, including your dev environment.
It also means that you can easily run your tests in a container that matches what'd be running in production. It reduces the risk of something working on GitHub's VM but blowing up in the container you're running on whatever infrastructure your production system is in.
So, if you can, just run a docker-compose from your action.