How can I build my image, run tests on it, and push it if they pass?

Hello, I’m hoping this is a very common use case but I haven’t quite been able to find the answer.

I have a dockerised rails app. I’d like to setup CI using GitHub Actions so that the docker image builds, the tests run within the image, and – assuming they pass – the image is pushed to docker hub.

I’ve had a workflow working for a while that builds and pushes the image. I suppose I could add a step to setup the database and another to run the tests, each using docker run?

By contrast, I spent a few hours today getting a rails test runner workflow working. I later realised it wouldn’t provide true parity since it doesn’t run in the docker container :confused: And right now if both jobs run, gems and node modules are fetched twice: once in the job runner and once in the docker build step within the job runner.

Surely there’s a common approach to this, right?

The common approach would be to run the tests on the image between the image build and push. If the tests fail, that is exit with a non-zero exit code, the further steps won’t run (unless you give them an if: condition that changes that), so an image that fails the tests won’t be pushed.

You can use the DB service as in your existing test.yml, just make sure that the app container gets attached to the same network, see the job.container.network variable.

I’m not familiar with Rails so I can’t say much about the test framework itself, if it accesses the app over network you could just have Docker publish the relevant port(s) on the runner and aim your tests at those.

Thanks. For that approach, I’ve two follow up questions:

  1. Does that mean I’d run the tests with a basic run step using docker run or something? e.g.:
- run: docker run --rm $GITHUB_REPOSITORY:latest rails db:setup && rails test

(Note it’s a 2-command process: one to setup the db and one to run the tests)

  1. I’d like tests to run on pull requests too, but of course never deploy them. Does that mean I have to have a duplicate workflow file with everything except the deploy logic, or is there a way to have a “test” workflow and a “deploy” workflow that runs if the tests pass?

If those commands both need to run inside the container you built that’d make sense. But you’ll definitely need to attach the container to the job network as I described above, and if you want to run two commands with an && connection you’ll need to invoke a shell. For example:

- run: docker run --rm --network ${{ job.container.network}} --entrypoint sh $GITHUB_REPOSITORY:latest -c "rails db:setup && rails test"

The easiest way would probably be to have everything in one workflow, and just skip the deploy steps based on some condition (e.g. if the event isn’t a push to a specific branch).