Running Docker Commands

Last updated
Tags Cloud Server v3.x Server v2.x

This page explains how to build Docker images for deployment and further testing. The examples on this page that use the Docker execution environment show how to start services in a remote docker environment.

Overview

To build Docker images for deployment using the Docker execution environment, you must use a special setup_remote_docker key which creates a separate environment for each build for security. This environment is remote, fully-isolated and has been configured to execute Docker commands. If your job requires docker or docker-compose commands, add the setup_remote_docker step into your .circleci/config.yml:

jobs:
  build:
    docker:
      - image: cimg/base:2022.06
    steps:
      # ... steps for building/testing app ...
      - setup_remote_docker:
          version: 20.10.14

When setup_remote_docker executes, a remote environment will be created, and your current primary container will be configured to use it. Then, any docker-related commands you use will be safely executed in this new environment.

Note: The use of the setup_remote_docker key is reserved for configs in which your primary executor is a docker container. If your executor is machine (and you want to use docker commands in your config) you do not need to use the setup_remote_docker key.

Specifications

The Remote Docker Environment has the following technical specifications (for CircleCI server installations, contact the systems administrator for specifications):

CPUs Processor RAM HD
2 Intel(R) Xeon(R) @ 2.3GHz 8GB 100GB

Run Docker commands using the machine executor

The example below shows how you can build a Docker image using the machine executor with the default image - this does not require the use of remote Docker:

version: 2.1

jobs:
 build:
   machine:
    image: ubuntu-2204:2022.04.2
   steps:
     - checkout
     # start proprietary DB using private Docker image
     # with credentials stored in the UI
     - run: |
         echo "$DOCKER_PASS" | docker login --username $DOCKER_USER --password-stdin
         docker run -d --name db company/proprietary-db:1.2.3

     # build the application image
     - run: docker build -t company/app:$CIRCLE_BRANCH .

     # deploy the image
     - run: docker push company/app:$CIRCLE_BRANCH

Run Docker commands using the Docker executor

The example below shows how you can build and deploy a Docker image for our demo docker project using the Docker executor, with remote Docker:

version: 2.1
jobs:
  build:
    docker:
      - image: cimg/go:1.17
        auth:
          username: mydockerhub-user
          password: $DOCKERHUB_PASSWORD  # context / project UI env-var reference
    steps:
      - checkout
      # ... steps for building/testing app ...

      - setup_remote_docker:
          version: 20.10.14
          docker_layer_caching: true

      # build and push Docker image
      - run: |
          TAG=0.1.$CIRCLE_BUILD_NUM
          docker build -t CircleCI-Public/circleci-demo-docker:$TAG .
          echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
          docker push CircleCI-Public/circleci-demo-docker:$TAG

Below is a break down of what is happening during this build’s execution:

  1. All commands are executed in the primary-container. (line 5)
  2. Once setup_remote_docker is called, a new remote environment is created, and your primary container is configured to use it. All docker-related commands are also executed in your primary container, but building/pushing images and running containers happens in the remote Docker Engine. (line 10)
  3. We enable Docker Layer Caching (DLC) here to speed up image building.
  4. We use project environment variables to store credentials for Docker Hub. (line 17)

Note: The CircleCI convenience images for the Docker executor come with the Docker CLI pre-installed. If you are using a third-party image for your primary container that doesn’t already have the Docker CLI installed, then you will need to install it as part of your job before calling any docker commands.

      # Install via apk on alpine based images
      - run:
          name: Install Docker client
          command: apk add docker-cli

Specify a Docker version for remote docker

To specify the Docker version, you can set it as a version attribute:

      - setup_remote_docker:
          version: 20.10.11

CircleCI supports multiple versions of Docker. The following are the available versions:

  • 20.10.14
  • 20.10.12
  • 20.10.11
  • 20.10.7
  • 20.10.6
  • 20.10.2
  • 19.03.13
  • 17.09.0-ce (default)

Note: The version key is not currently supported on CircleCI server installations. Contact your system administrator for information about the Docker version installed in your remote Docker environment.

Separation of environments

The job and remote docker run in separate environments. Therefore, Docker containers specified to run your jobs cannot directly communicate with containers running in remote docker.

Accessing services

It is not possible to start a service in remote docker and ping it directly from a primary container or to start a primary container that can ping a service in remote docker. To solve that, you’ll need to interact with a service from remote docker, as well as through the same container:

# ...
      - run:
          name: "Start Service and Check That it’s Running"
          command: |
            docker run -d --name my-app my-app
            docker exec my-app curl --retry 10 --retry-connrefused http://localhost:8080
# ...

A different way to do this is to use another container running in the same network as the target container:

# ...
      - run: |
          docker run -d --name my-app my-app
          docker run --network container:my-app appropriate/curl --retry 10 --retry-connrefused http://localhost:8080
# ...

Mounting folders

It is not possible to mount a volume from your job space into a container in Remote Docker (and vice versa). You may use the docker cp command to transfer files between these two environments. For example, to start a container in Remote Docker using a config file from your source code:

- run: |
    # create a dummy container which will hold a volume with config
    docker create -v /cfg --name configs alpine:3.4 /bin/true
    # copy a config file into this volume
    docker cp path/in/your/source/code/app_config.yml configs:/cfg
    # start an application container using this volume
    docker run --volumes-from configs app-image:1.2.3

In the same way, if your application produces some artifacts that need to be stored, you can copy them from Remote Docker:

- run: |
    # start container with the application
    # make sure you're not using `--rm` option otherwise the container will be killed after finish
    docker run --name app app-image:1.2.3

- run: |
    # after application container finishes, copy artifacts directly from it
    docker cp app:/output /path/in/your/job/space

It is also possible to use https://github.com/outstand/docker-dockup or a similar image for backup and restore to spin up a container as shown in the following example circle-dockup.yml config:

version: '2'
services:
 bundler-cache:
   image: outstand/dockup:latest
   command: restore
   container_name: bundler-cache
   tty: true
   environment:
     COMPRESS: 'false'
   volumes:
     - bundler-data:/source/bundler-data

Then, the sample CircleCI .circleci/config.yml snippets below populate and back up the bundler-cache container.

# Populate bundler-data container from circleci cache
- restore_cache:
    keys:
      - v4-bundler-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
      - v4-bundler-cache-{{ arch }}-{{ .Branch }}
      - v4-bundler-cache-{{ arch }}
- run:
    name: Restoring bundler cache into docker volumes
    command: |
      NAME=bundler-cache
      CACHE_PATH=~/bundler-cache
      set -x
      mkdir -p $CACHE_PATH
      docker-compose -f docker-compose.yml -f docker/circle-dockup.yml up --no-start $NAME
      docker cp $CACHE_PATH/. $NAME:/backup
      docker-compose -f docker-compose.yml -f docker/circle-dockup.yml up --no-recreate $NAME
      docker rm -f $NAME

# Back up the same volume to circle cache
- run:
    name: Backing up bundler cache from docker volumes
    command: |
      NAME=bundler-cache
      CACHE_PATH=~/bundler-cache
      set -x
      docker-compose -f docker-compose.yml -f docker/circle-dockup.yml run --name $NAME $NAME backup
      docker cp $NAME:/backup/. $CACHE_PATH
      docker rm -f $NAME
- save_cache:
    key: v4-bundler-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
    paths:
      - ~/bundler-cache

Accessing the remote docker environment

When a remote Docker environment is spun up, an SSH alias is created for you so you can SSH into the remote Docker virtual machine. This may be helpful for debugging your builds, or modifying the Docker or VM filesystem configuration. To SSH into the remote Docker VM, run the following within your project configuration job steps, or during a SSH rerun:

ssh remote-docker

Note: The example shown above provides a way for you to utilize volume mounts since they don’t work in the docker executor. An alternative to this approach is to use the machine executor where volume mounts do work.

Thanks to ryansch for contributing this example.

See also

Docker Layer Caching

job-space

primary-container

docker-layer-caching



Help make this document better

This guide, as well as the rest of our docs, are open source and available on GitHub. We welcome your contributions.

Need support?

Our support engineers are available to help with service issues, billing, or account related questions, and can help troubleshoot build configurations. Contact our support engineers by opening a ticket.

You can also visit our support site to find support articles, community forums, and training resources.