Preface
If are you using CirceCI as a continuous integration tool, and your application has external dependencies either as a database
or a mocked service already containerized, there are two ways you can choose. The easy way is to have a machine
executor, without any pain and gain. The ubuntu
image already comes with installed docker, you can use it, all you
need is to install docker-compose
if you need it. The hard way is to set up a docker
executor, to build, compose and
run your components (spoiler: and your tests too).
Limitations
- CircleCI doesn’t support
volumes
. It means, for instance, you cannot define your SQL-init script with it. - Another pain point, is the networking. Your primary container can’t reach other containers through the docker network.
- Waiting for container startup can be solved by third-party extensions, but no direct support for it from CircleCI.
Step by step
Choosing the right image
The primary image for CircleCI is where commands are executed from config.yml
. I’d prefer something which already
has docker cli
, for instance:
test-circle-ci:
docker:
- image: docker:17.05.0-ce-git
resource_class: small
The docker
docker image is an Alpine-based image, with a minimal footprint. You don’t have neither curl
nor docker-compose
Checkout
Ok, it does nothing with the dockerized configuration, but it should be done.
steps:
- checkout
...
Setup remote docker
This is where it begins. You will not use the docker daemon
from your primary container. This command connects your
container with CircleCI’s remote docker
solution.
steps:
...
- setup_remote_docker:
docker_layer_caching: true
version: 20.10.12
...
Without the version
parameter you can experience connection issues from docker-compose
. Which is tricky,
because docker build
is still working without it.
Install docker-compose
As I already mentioned it in the image
part, the chosen image is so small, it doesn’t have a curl
,
a docker-compose
or sudo
. At least you already root in the image.
steps:
...
- run:
name: Download and install docker compose
command: |
apk update && apk upgrade && apk add curl
curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64" -o ~/docker-compose
chmod +x ~/docker-compose
mv ~/docker-compose /usr/local/bin/docker-compose
...
Building the test container
Because you cannot connect to the remotely executed docker containers from your CircleCI primary image, you need a test
container, which can be executed as part of the docker-network
. I hope you already prepared a Dockerfile
in your
project root. Keep it simple as possible, copy your sources, resources, dependencies, test and config files, and set up
your WORKDIR
, and environment variables.
steps:
...
- run:
name: Build test image
command: |
docker build -t tests .
...
Preparing the dependencies
Do you have a docker-compose.yaml
already? It’s time to spin it up!
steps:
...
- run:
name: Spin up database
command: |
docker-compose down -v
docker-compose up -d
...
Looks easy, doesn’t it? But don’t forget about the missing feature, the volumes
are not supported via remote-docker
Init database
Let’s wait while the database spins up, and initialize it with docker exec
command
steps:
...
- run:
name: prepare database
command: |
while ! docker exec database mysql -e 'select 1=1' -proot test_db; do
echo "waiting for database"
sleep 2
done
docker cp test/resources/init.sql database:/init.sql
docker exec database mysql -e 'source /init.sql' -proot test_db
...
Execute all the tests!
I’m a Clojure developer, so I have an alias in my deps.edn
for circe-ci-test
. Let’s run it.
steps:
...
- run:
name: testing
command: |
docker run --network container:database tests clj -M:circle-ci-test
...
Tear down
Be a nice guy, and clean up the mess you did:
steps:
...
- run:
name: tear-down
command: |
docker-compose down -v
...
All together
version: 2.1
jobs:
test_ci:
docker:
- image: docker:17.05.0-ce-git
resource_class: small
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: true
version: 20.10.12
- run:
name: Download and install docker compose
command: |
apk update && apk upgrade && apk add curl
curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64" -o ~/docker-compose
chmod +x ~/docker-compose
mv ~/docker-compose /usr/local/bin/docker-compose
- run:
name: Build test image
command: |
docker build -t tests .
- run:
name: Spin up database
command: |
docker-compose down -v
docker-compose up -d
- run:
name: prepare database
command: |
while ! docker exec database mysql -e 'select 1=1' -proot test_db; do
echo "waiting for database"
sleep 2
done
docker cp test/resources/init.sql database:/init.sql
docker exec database mysql -e 'source /init.sql' -proot test_db
- run:
name: testing
command: |
docker run --network container:database tests clj -M:circle-ci-test
- run:
name: tear-down
command: |
docker-compose down -v
workflows:
version: 2
ci_tests:
jobs:
- test-circle-ci:
filters:
branches:
ignore: master
test-deploy:
jobs:
- test_ci:
filters:
branches:
only: master
- deploy:
requires:
- test_ci
filters:
branches:
only: master
Epilogue
Of course these steps come only from my experience, you can have different problems and solutions for it. These
steps are working for me even on CirceCI cloud, and at my local with command.
circleci local execute --job test-circle-ci