Using LXD with the Custom executor

Tier: Free, Premium, Ultimate Offering:, Self-managed

In this example, we use LXD to create a container per build and clean it up afterwards.

This example uses a bash script for each stage. You can specify your own image, which is exposed as CI_JOB_IMAGE. This example uses the ubuntu:18.04 image for simplicity. If you want to support multiple images, you would have to modify the executor.

These scripts have the following prerequisites:


  name = "lxd-driver"
  url = ""
  token = "xxxxxxxxxxx"
  executor = "custom"
  builds_dir = "/builds"
  cache_dir = "/cache"
    prepare_exec = "/opt/lxd-driver/" # Path to a bash script to create lxd container and download dependencies.
    run_exec = "/opt/lxd-driver/" # Path to a bash script to run script inside the container.
    cleanup_exec = "/opt/lxd-driver/" # Path to bash script to delete container.


Each stage prepare, run, and cleanup will use this script to generate variables that are used throughout the scripts.

It’s important that this script is located in the same directory as the other scripts, in this case /opt/lxd-driver/.

#!/usr/bin/env bash

# /opt/lxd-driver/



The prepare script will do the following:

  • Destroy a container with the same name if there is one running.
  • Start a container and wait for it to start.
  • Install prerequisite dependencies.
#!/usr/bin/env bash

# /opt/lxd-driver/

currentDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${currentDir}/ # Get variables from base.

set -eo pipefail

# trap any error, and mark it as a system failure.

start_container () {
    if lxc info "$CONTAINER_ID" >/dev/null 2>/dev/null ; then
        echo 'Found old container, deleting'
        lxc delete -f "$CONTAINER_ID"

    # The container image is hardcoded, but you can use
    # the `CI_JOB_IMAGE` predefined variable
    # which is available under `CUSTOM_ENV_CI_JOB_IMAGE` to allow the
    # user to specify the image. The rest of the script assumes that
    # you are running on an ubuntu image so modifications might be
    # required.
    lxc launch ubuntu:18.04 "$CONTAINER_ID"

    # Wait for container to start, we are using systemd to check this,
    # for the sake of brevity.
    for i in $(seq 1 10); do
        if lxc exec "$CONTAINER_ID" -- sh -c "systemctl isolate" >/dev/null 2>/dev/null; then

        if [ "$i" == "10" ]; then
            echo 'Waited for 10 seconds to start container, exiting..'
            # Inform GitLab Runner that this is a system failure, so it
            # should be retried.
            exit "$SYSTEM_FAILURE_EXIT_CODE"

        sleep 1s

install_dependencies () {
    # Install Git LFS, git comes pre installed with ubuntu image.
    lxc exec "$CONTAINER_ID" -- sh -c 'curl -s "" | sudo bash'
    lxc exec "$CONTAINER_ID" -- sh -c "apt-get install -y git-lfs"

    # Install gitlab-runner binary since we need for cache/artifacts.
    lxc exec "$CONTAINER_ID" -- sh -c 'curl -L --output /usr/local/bin/gitlab-runner ""'
    lxc exec "$CONTAINER_ID" -- sh -c "chmod +x /usr/local/bin/gitlab-runner"

echo "Running in $CONTAINER_ID"




This will run the script generated by GitLab Runner by sending the content of the script to the container via STDIN.

#!/usr/bin/env bash

# /opt/lxd-driver/

currentDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${currentDir}/ # Get variables from base.

lxc exec "$CONTAINER_ID" /bin/bash < "${1}"
if [ $? -ne 0 ]; then
    # Exit using the variable, to make the build as failure in GitLab
    # CI.


Destroy the container since the build has finished.

#!/usr/bin/env bash

# /opt/lxd-driver/

currentDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${currentDir}/ # Get variables from base.

echo "Deleting container $CONTAINER_ID"

lxc delete -f "$CONTAINER_ID"