We have found it valuable to build project-specific Docker images, using Packer, and using these to drive our CI process. The key reason for this is it allows us to prime the CI image with exactly the pieces our project needs, so that each CI pipeline doesn’t need to set everything up from scratch.
# In a Drumkit-enabled project, do:
make init-project-packer # Initialize packer scripts
docker login registry.gitlab.com # Authenticate to GitLab registry
make ci-images # Build and push CI images
Then use the resulting image in your .gitlab-ci.yml
jobs:
image:
name: registry.gitlab.com/hres/drupal-sites/submanager/submanager:latest
entrypoint: [""] # We have to override the container entrypoint or else we end up in /bin/sh and `. d` doesn't work. See https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#overriding-the-entrypoint-of-an-image
Drumkit includes a number of helpers to scaffold this setup for you. The key pieces are:
make ci-images
setupTBD: except for the top-level one, these should probably move up into drumkit
Drumkit has a project init target that will bootstrap the Packer-driven build scripts described below. Here’s how to use it:
# If you haven't already, install and initialize Drumkit
wget -O - https://drumk.it/installer
. d
# Then initialize packer scripts in your project:
make init-project-packer
This will prompt you for the name of your project, and a Docker image registry URL, which for
GitLab will look something like: registry.gitlab.com/GROUP/PROJECT
.
These 2 values will be used to render a couple of templates, and the rest of
the boilerplate Packer .json
config files and associated .sh
provisioning
scripts for CI images copied into place in your project.
You should review the scripts/packer/json/40-PROJECT.json
config and
scripts/packer/scripts/PROJECT.sh
scripts and tailor them to your project,
then you can build and push your CI images like so:
docker login registry.gitlab.com
make ci-images
This will loop over all available scripts/packer/json/*.json
files in order,
and run a packer build
on each, to build and push the successive layers.
Typically this is:
10-bionic.json
- bare bones setup on to of Ubuntu Bionic 18.0420-base.json
- basic Apt and other OS-level packages30-php.json
- Apache / PHP40-project.json
- Project-custom setup - typically building the codebaseFinally, you can use the resulting Docker image in your .gitlab-ci.yml
jobs
like this:
image:
name: registry.gitlab.com/hres/drupal-sites/submanager/submanager:latest
entrypoint: [""] # We have to override the container entrypoint or else we end up in /bin/sh and `. d` doesn't work. See https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#overriding-the-entrypoint-of-an-image
Your GitLab project should have a Docker image registry enabled by default, assuming your instance has the feature enabled.
While it is possible to automate authenticating to your image registry, the
simplest solution is to do a docker login
before building the images below,
using your regular gitlab credentials. This just authenticates that you have
access to the project and permission to push container images:
docker login registry.gitlab.com
The idea is to build up layers of Docker images, to build up your technology stack. Each is built up based on the previous layer, but also easy to override if you need to customize what a layer does.
For example, as Drupal developers, we typically want a LAMP stack, which the core Drumkit targets provide:
10-bionic.json
- base Ubuntu 18.04 image (runs apt.sh
and purge-extra-packages.sh
)20-base.json
- utilities (runs utils.sh
)30-php.json
- set up Apache, MySQL, PHP, it’s libraries, and Composer (runs php.sh
)40-project.json
- project-level setup, usually just a make build
of your project.Example:
{
"builders": [
{
"type": "docker",
"image": "registry.gitlab.com/[GROUP]/[PROJECT]/php:latest",
"commit": true
}
],
"provisioners": [
{
"type": "shell",
"inline": "mkdir -p /var/www/[PROJECT]"
},
{
"destination": "/var/www/[PROJECT]",
"source": "./.clone/",
"type": "file"
},
{
"type": "shell",
"scripts": [
"scripts/packer/scripts/[PROJECT].sh",
"scripts/packer/scripts/cleanup.sh"
]
}
],
"post-processors": [
[
{
"type": "docker-tag",
"repository": "registry.gitlab.com/[GROUP]/[PROJECT]/{{user `image_name`}}",
"tag": "0.0.x"
},
{
"type": "docker-tag",
"repository": "registry.gitlab.com/[GROUP]/[PROJECT]/{{user `image_name`}}",
"tag": "latest"
},
{
"type": "docker-push"
}
]
],
"variables": {
"image_name": "cv"
}
}
Scripts:
apt.sh
- configure Apt to install minimal packages, and other housekeepingpurge-extra-packages.sh
- purge unnecessary packagesphp.sh
- install Apache, PHP, and related modules/librariescleanup.sh
- runs after each layer, to do an `apt-get autoremove &&project.sh
- run project-specific build steps, to pre-load the CI image with needed artifactsExample:
#!/bin/bash
# Steps for setting up CV inside a CI docker image at packer time.
# Run a composer install to pre-populate its cache, which should speed up the process in CI.
cd /var/www/[PROJECT]
. d
make build VERBOSE=1
.clone target - for mysterious Packer reasons (that we are sure exist), we need
to clone the local project working dir into .clone
, and have Packer work on
those.