Today, there are three different options for building Docker images in GitLab CI, and each of them comes with security and complexity tradeoffs. Depot fixes a lot of these tradeoffs, and can be used to build images in GitLab CI quickly and securely.
Current options for building Docker images in GitLab CI
There are three different options for building images in GitLab CI/CD jobs:
- You can use the
shell
executor on your own GitLab Runners. This requires configuring the Docker Engine and granting thegitlab-runner
user full root permissions to invokedocker
commands. - You can use Docker in Docker (
dind
) with registered runners. This uses thedind
image provided by Docker, so it contains all thedocker
tools. To build new containers withdind
, you have to run the runner container in privileged mode. - Or, you can bind to the Docker socket by bind mounting
/var/run/docker.sock
into the container where your build is running.
All these options come with tradeoffs and downsides.
The first option requires you to manually configure your own runners and grant them root permissions. The second option requires that each build job get its own instance of the Docker engine, so jobs are slower because there is no layer caching, and privileged mode is still required. The third option requires you to bind mount the Docker socket into your container, which exposes the underlying host and any other processes on that host to privilege escalation.
Building Docker images in GitLab CI with Depot
With Depot, you can build your Docker images from GitLab CI/CD jobs without needing to configure shell
executor runners, work around slow builds with dind
, or bind mount the Docker socket. Instead, the depot
CLI builds the container using Depot's remote builders, and can optionally push the resulting image to a registry.
The Depot CLI can be installed via a before_script
:
before_script:
- curl https://depot.dev/install-cli.sh | DEPOT_INSTALL_DIR=/usr/local/bin sh
After the CLI is installed, you can build your image with the depot build
command:
script:
- depot build -t org/image:tag .
Most likely, you will want to push your image to a registry, with the depot build --push
flag. Depot uses the local Docker registry credentials when pushing, so you will need to configure those credentials using one of two options: using the docker
CLI or creating the configuration manually.
Registry authentication with docker login
The easiest way to configure registry authentication is to use the docker login
command. This will create a ~/.docker/config.json
file that Depot will use to authenticate with your registry. Note that this requires running the Docker daemon with Docker-in-Docker (dind
) to perform the login. However, you do not need to run the GitLab CI runner in privileged mode, the daemon is only used for registry login:
image: docker:20.10.16
services:
- docker:20.10.16-dind
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: '/certs'
build-job:
before_script:
- apk add --no-cache curl # install curl as it is not available in the docker:20.10.16 image
- curl https://depot.dev/install-cli.sh | DEPOT_INSTALL_DIR=/usr/local/bin sh
script:
- docker login registry.gitlab.com --username your-username --password $REGISTRY_TOKEN
- depot build -t registry.gitlab.com/your-username/myimage:$CI_COMMIT_SHA . --push
variables:
DEPOT_TOKEN: $DEPOT_TOKEN
With this workflow, you can build native multi-platform images directly from GitLab CI and push them to a private registry. The depot
CLI is installed via curl
and the dind
service is configured with TLS enabled. Giving you the ability to run docker login
to authenticate to a private registry, but leave the build part to Depot.
The Docker-in-Docker service is only used for authentication and not for the actual build. This means you get significantly faster builds because Depot manages the caching of layers for you, unlike dind
which has no layer caching when you use docker build
.
Registry authentication via ~/.docker/config.json
If you already have a Docker config.json
file with your registry credentials, you can skip the docker login
step and provide the config contents directly via a DOCKER_AUTH_CONFIG
environment variable. Your workflow can save the contents of that variable to $HOME/.docker/config.json
, and Depot will read from that file when pushing. This removes the need for the docker
CLI and dind
service at all in your image build:
build-image:
before_script:
- curl https://depot.dev/install-cli.sh | DEPOT_INSTALL_DIR=/usr/local/bin sh
- mkdir -p $HOME/.docker
- echo $DOCKER_AUTH_CONFIG > $HOME/.docker/config.json
script:
- depot build -t registry.gitlab.com/repo/image:tag . --push
variables:
DEPOT_TOKEN: $DEPOT_TOKEN
Before the depot build
runs, contents of the DOCKER_AUTH_CONFIG
environment variable are saved as $HOME/.docker/config.json
.
You may be able to generate a config.json
file locally by running docker login
and then copying the contents of the $HOME/.docker/config.json
file and saving it as DOCKER_AUTH_CONFIG
, if you have static authentication credentials for your registry.
Conclusion
There are multiple options for building Docker images in GitLab CI, each with security and complexity tradeoffs. They are not impossible to work around, but they are inconvenient and cause a lot of friction. This friction can manifest as slow builds, security concerns, or having to maintain complex build infrastructure. It's totally unnecessary.
Instead, you can use Depot to build Docker images in GitLab CI quickly and securely, using Depot's remote builders, without needing to configure shell
executor runners, work around slow builds with dind
, or bind mount the Docker socket, and all without having to grant root access to your CI runners.
If your interested in trying out Depot, we offer a 14-day free trial with no credit card required.