Creating a Conan package using a Docker runner

Warning

This feature is experimental and subject to breaking changes. See the Conan stability section for more information.

First of all you need to have the Docker daemon installed and running, plus Conan and the docker Python package. This tutorial assumes that you are running Conan inside a Python virtual environment, skip the first line if you already have the docker Python package installed in your virtual environment.

  1. # install docker in your virtual environment if you don't have it already installed
  2. $ pip install conan docker
  3. $ docker ps
  4. $ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Now we are going to create create simple cmake_lib Conan template to later run inside Docker using the runner feature. Let’s create the Conan package and a Dockerfile inside our project folder.

  1. $ cd </my/runner/folder>
  2. $ mkdir mylib
  3. $ cd mylib
  4. $ conan new cmake_lib -d name=mylib -d version=0.1
  5. $ tree
  6. .
  7. ├── CMakeLists.txt
  8. ├── conanfile.py
  9. ├── include
  10. └── mylib.h
  11. ├── src
  12. └── mylib.cpp
  13. └── test_package
  14. ├── CMakeLists.txt
  15. ├── conanfile.py
  16. └── src
  17. └── example.cpp

Dockerfile

  1. FROM ubuntu:22.04
  2. RUN apt-get update \
  3. && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
  4. build-essential \
  5. cmake \
  6. python3 \
  7. python3-pip \
  8. python3-venv \
  9. && rm -rf /var/lib/apt/lists/*
  10. RUN pip install conan
  1. $ cd </my/runner/folder>/mylib
  2. $ tree
  3. .
  4. ...
  5. ├── Dockerfile
  6. ...

Now, we need to define two new profiles inside the conan profiles folder. Replace </my/runner/folder> with your real project folder path.

docker_example_host profile

  1. [settings]
  2. arch=x86_64
  3. build_type=Release
  4. compiler=gcc
  5. compiler.cppstd=gnu17
  6. compiler.libcxx=libstdc++11
  7. compiler.version=11
  8. os=Linux
  9. [runner]
  10. type=docker
  11. dockerfile=</my/runner/folder>/mylib
  12. cache=copy
  13. remove=true

docker_example_build profile

  1. [settings]
  2. arch=x86_64
  3. build_type=Release
  4. compiler=gcc
  5. compiler.cppstd=gnu17
  6. compiler.libcxx=libstdc++11
  7. compiler.version=11
  8. os=Linux

We are going to start from a totally clean environment, without any containers, images or conan package.

  1. $ conan list "*:*"
  2. Found 0 pkg/version recipes matching * in local cache
  1. $ docker ps --all
  2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  1. $ docker images
  2. REPOSITORY TAG IMAGE ID CREATED SIZE

Now, it’s time to create our library mylib using our new runner definition.

  1. $ conan create . --version 0.1 -pr:h docker_example_host -pr:b docker_example_build

If we split and analyze the command output, we can see what is happening and where the commands are being executed.

1. Standard conan execution.

  1. ======== Exporting recipe to the cache ========
  2. mylib/0.1: Exporting package recipe: </my/runner/folder>/mylib/conanfile.py
  3. mylib/0.1: Copied 1 '.py' file: conanfile.py
  4. mylib/0.1: Copied 1 '.txt' file: CMakeLists.txt
  5. mylib/0.1: Copied 1 '.h' file: mylib.h
  6. mylib/0.1: Copied 1 '.cpp' file: mylib.cpp
  7. mylib/0.1: Exported to cache folder: /Users/davidsanfal/.conan2/p/mylib4abd06a04bdaa/e
  8. mylib/0.1: Exported: mylib/0.1#8760bf5a311f01cc26f3b95428203210 (2024-07-08 12:22:01 UTC)
  9. ======== Input profiles ========
  10. Profile host:
  11. [settings]
  12. arch=x86_64
  13. build_type=Release
  14. compiler=gcc
  15. compiler.cppstd=gnu17
  16. compiler.libcxx=libstdc++11
  17. compiler.version=11
  18. os=Linux
  19. Profile build:
  20. [settings]
  21. arch=x86_64
  22. build_type=Release
  23. compiler=gcc
  24. compiler.cppstd=gnu17
  25. compiler.libcxx=libstdc++11
  26. compiler.version=11
  27. os=Linux

2. Build docker image

  1. **********************************************
  2. * Building the Docker image: my-conan-runner *
  3. **********************************************
  4. Dockerfile path: '</my/runner/folder>/mylib/Dockerfile'
  5. Docker build context: '</my/runner/folder>/mylib'
  6. Step 1/4 : FROM ubuntu:22.04
  7. ...
  8. ---> 2bcf70201cce
  9. Successfully built 2bcf70201cce
  10. Successfully tagged conan-runner-default:latest

3. Save the local cache running conan cache save.

  1. ***********************************************************************************
  2. * Save host cache in: </my/runner/folder>/mylib/.conanrunner/local_cache_save.tgz *
  3. ***********************************************************************************
  4. Found 1 pkg/version recipes matching * in local cache
  5. Saving mylib/0.1: mylib4abd06a04bdaa

4. Create and initialize the docker container.

  1. *********************************
  2. * Creating the docker container *
  3. *********************************
  4. *****************************************
  5. * Container conan-runner-docker running *
  6. *****************************************

5. Check if the container has a conan version with the runner feature.

  1. *******************************************
  2. * Running in container: "conan --version" *
  3. *******************************************
  4. Conan version 2.5.0

6. Initialize the container conan cache using the host copy running conan cache restore.

  1. ***********************************************************************************************************
  2. * Running in container: "conan cache restore "/root/conanrunner/mylib/.conanrunner/local_cache_save.tgz"" *
  3. ***********************************************************************************************************
  4. Restore: mylib/0.1 in mylib4abd06a04bdaa
  5. Local Cache
  6. mylib
  7. mylib/0.1
  8. revisions
  9. 8760bf5a311f01cc26f3b95428203210 (2024-07-08 12:22:19 UTC)
  10. packages
  11. recipe_folder: mylib4abd06a04bdaa

7. Run the conan create inside the container and build “mylib”.

  1. *********************************************************************************************************************************************************
  2. * Running in container: "conan create /root/conanrunner/mylib --version 0.1 -pr:h docker_example_host -pr:b docker_example_build -f json > create.json" *
  3. *********************************************************************************************************************************************************
  4. ======== Exporting recipe to the cache ========
  5. mylib/0.1: Exporting package recipe: /root/conanrunner/mylib/conanfile.py
  6. mylib/0.1: Copied 1 '.py' file: conanfile.py
  7. mylib/0.1: Copied 1 '.txt' file: CMakeLists.txt
  8. mylib/0.1: Copied 1 '.cpp' file: mylib.cpp
  9. mylib/0.1: Copied 1 '.h' file: mylib.h
  10. mylib/0.1: Exported to cache folder: /root/.conan2/p/mylib4abd06a04bdaa/e
  11. mylib/0.1: Exported: mylib/0.1#8760bf5a311f01cc26f3b95428203210 (2024-07-08 12:22:20 UTC)
  12. ======== Input profiles ========
  13. Profile host:
  14. [settings]
  15. arch=x86_64
  16. build_type=Release
  17. compiler=gcc
  18. compiler.cppstd=gnu17
  19. compiler.libcxx=libstdc++11
  20. compiler.version=11
  21. os=Linux
  22. Profile build:
  23. [settings]
  24. arch=x86_64
  25. build_type=Release
  26. compiler=gcc
  27. compiler.cppstd=gnu17
  28. compiler.libcxx=libstdc++11
  29. compiler.version=11
  30. os=Linux
  31. ======== Computing dependency graph ========
  32. Graph root
  33. cli
  34. Requirements
  35. mylib/0.1#8760bf5a311f01cc26f3b95428203210 - Cache
  36. ======== Computing necessary packages ========
  37. mylib/0.1: Forced build from source
  38. Requirements
  39. mylib/0.1#8760bf5a311f01cc26f3b95428203210:8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe - Build
  40. ======== Installing packages ========
  41. -------- Installing package mylib/0.1 (1 of 1) --------
  42. ...
  43. [ 50%] Building CXX object CMakeFiles/example.dir/src/example.cpp.o
  44. [100%] Linking CXX executable example
  45. [100%] Built target example
  46. ======== Testing the package: Executing test ========
  47. mylib/0.1 (test package): Running test()
  48. mylib/0.1 (test package): RUN: ./example
  49. mylib/0.1: Hello World Release!
  50. mylib/0.1: __x86_64__ defined
  51. mylib/0.1: _GLIBCXX_USE_CXX11_ABI 1
  52. mylib/0.1: __cplusplus201703
  53. mylib/0.1: __GNUC__11
  54. mylib/0.1: __GNUC_MINOR__4
  55. mylib/0.1 test_package

8. Copy just the package created inside the container using the pkglist.json info from the previous conan create, restore this new package inside the host cache running a conan cache save and remove the container.

  1. ************************************************************************************************************************************
  2. * Running in container: "conan cache save --list=pkglist.json --file "/root/conanrunner/mylib"/.conanrunner/docker_cache_save.tgz" *
  3. ************************************************************************************************************************************
  4. Saving mylib/0.1: mylib4abd06a04bdaa
  5. Saving mylib/0.1:8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe: b/mylib503035e4ee8ae/p
  6. Saving mylib/0.1:8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe metadata: b/mylib503035e4ee8ae/d/metadata
  7. Local Cache
  8. mylib
  9. mylib/0.1
  10. revisions
  11. 8760bf5a311f01cc26f3b95428203210 (2024-07-08 12:22:20 UTC)
  12. packages
  13. 8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe
  14. revisions
  15. ded6547554ff2306db5250451340fa43
  16. package_folder: b/mylib503035e4ee8ae/p
  17. metadata_folder: b/mylib503035e4ee8ae/d/metadata
  18. info
  19. settings
  20. os: Linux
  21. arch: x86_64
  22. compiler: gcc
  23. compiler.cppstd: gnu17
  24. compiler.libcxx: libstdc++11
  25. compiler.version: 11
  26. build_type: Release
  27. options
  28. fPIC: True
  29. shared: False
  30. recipe_folder: mylib4abd06a04bdaa
  31. ******************************************************************************************
  32. * Restore host cache from: </my/runner/folder>/mylib/.conanrunner/docker_cache_save.tgz *
  33. ******************************************************************************************
  34. Restore: mylib/0.1 in mylib4abd06a04bdaa
  35. Restore: mylib/0.1:8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe in b/mylib503035e4ee8ae/p
  36. Restore: mylib/0.1:8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe metadata in b/mylib503035e4ee8ae/d/metadata
  37. **********************
  38. * Stopping container *
  39. **********************
  40. **********************
  41. * Removing container *
  42. **********************

If we now check the status of our conan and docker cache, we will see the new mylib package compile for Linux and the new docker image but we don’t have any container because we define remove=true

  1. $ conan list "*:*"
  2. Found 1 pkg/version recipes matching * in local cache
  3. Local Cache
  4. mylib
  5. mylib/0.1
  6. revisions
  7. 8760bf5a311f01cc26f3b95428203210 (2024-07-08 12:33:28 UTC)
  8. packages
  9. 8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe
  10. info
  11. settings
  12. arch: x86_64
  13. build_type: Release
  14. compiler: gcc
  15. compiler.cppstd: gnu17
  16. compiler.libcxx: libstdc++11
  17. compiler.version: 11
  18. os: Linux
  19. options
  20. fPIC: True
  21. shared: False
  1. $ docker ps --all
  2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  1. $ docker images
  2. REPOSITORY TAG IMAGE ID CREATED SIZE
  3. my-conan-runner latest 2bcf70201cce 11 minutes ago 531MB

What we have just done is to compile a library from scratch inside a Docker container without running any Docker command and retrieve the generated packages in a totally transparent and easily debuggable way thanks to our terminal output.

In this way, we can work as we have always done regardless of whether it is on our machine or in a container, without several open terminals and having the result of each operation in the same cache, being able to reuse the compiled packages from a previous compilation in another container automatically and transparently.