January 24, 2020

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 a Dockerfile that includes Node and all that. Hence the build: . which just builds the Dockerfile 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 a docker login step with a docker 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.

Comments powered by Disqus