DEV Community

Cover image for Combining CMake with Docker
pikoTutorial
pikoTutorial

Posted on • Originally published at pikotutorial.com

Combining CMake with Docker

Welcome to the next pikoTutorial!

In one of the recent articles, I showed how to use CMake for setting up a Python project. Today, we will see how to further extend it by adding building of Docker images to the CMakeLists.txt files. Today's project structure:

project/ ├── app1/ │ ├── CMakeLists.txt │ ├── Dockerfile │ ├── main.py │ ├── requirements.txt ├── app2/ │ ├── CMakeLists.txt │ ├── Dockerfile │ ├── main.py │ ├── requirements.txt └── build/ ├── CMakeLists.txt 
Enter fullscreen mode Exit fullscreen mode

Top CMakeLists.txt file

Nothing special here, just assuring Python availability and adding sub-directories to the build:

# specify minimum CMake version cmake_minimum_required(VERSION 3.28) # specify project name project(CMakeWithDocker) # find Python find_package(Python3 REQUIRED COMPONENTS Interpreter) # include all subdirectoies into the build add_subdirectory(app1) add_subdirectory(app2) 
Enter fullscreen mode Exit fullscreen mode

Dockerfiles

Both app1/Dockerfile and app2/Dockerfile look the same:

FROM python:3.9-slim WORKDIR /app COPY . /app RUN pip install --no-cache-dir -r requirements.txt CMD ["python", "main.py"] 
Enter fullscreen mode Exit fullscreen mode

CMake files

Both app1/CMakeLists.txt and app2/CMakeLists.txt look similar with the only difference being the application name:

set(IMAGE_TARGET image_app_1) # add custom target to build image for app1 add_custom_target(${IMAGE_TARGET} ALL WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/app1 COMMAND docker build -t image_app_1 . COMMENT "Building image for app1" ) 
Enter fullscreen mode Exit fullscreen mode

Pay attention to ALL placed after the target's name. CMake, by default, doesn't build custom targets, so you must make them explicitly dependent on all target (the default CMake target executed when calling cmake without --target flag).

Building the project

Now when everything's ready, you can build the project by calling:

cd build cmake .. cmake --build . -j 
Enter fullscreen mode Exit fullscreen mode

Note for beginners: because both of our applications are independent from each other, I'm adding -j to the build command to parallelize build and speed up the whole process.

After the build is done, you can run:

docker images 
Enter fullscreen mode Exit fullscreen mode

to check that 2 new images appeared:

REPOSITORY TAG IMAGE ID CREATED SIZE image_app_1 latest 150d015d8915 3 minutes ago 150MB image_app_2 latest d16b1453dbcc 3 minutes ago 147MB 
Enter fullscreen mode Exit fullscreen mode

You can run them calling, e.g.:

docker run --rm --name container_app_1 image_app_1 
Enter fullscreen mode Exit fullscreen mode

Adding a dependent target

It's often a good idea to initialize the project, build, run tests etc. before building the final image. Here, as an example, I'll add initialization of the virtual environment of each Python application as a mandatory step for building the images:

set(VENV_TARGET venv_app_1) set(IMAGE_TARGET image_app_1) # add custom target to create virtual environment add_custom_target(${VENV_TARGET} ALL WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/app1 COMMAND ${Python3_EXECUTABLE} -m venv venv COMMENT "Creating virtual environment for app1" ) # add custom target to build image for app1 add_custom_target(${IMAGE_TARGET} ALL WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/app1 COMMAND docker build -t image_app_1 . COMMENT "Building image for app1" DEPENDS ${VENV_TARGET} ) 
Enter fullscreen mode Exit fullscreen mode

Notice that here I had to add DEPENDS argument to image_app_1 custom target definition. It assures that venv_app_1 target will be completed before image starts to build.

Running Docker container with CMake

But why stop here? Let's add the target for running the container from CMake level:

set(VENV_TARGET venv_app_1) set(IMAGE_TARGET image_app_1) # add custom target to create virtual environment add_custom_target(${VENV_TARGET} ALL WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/app1 COMMAND ${Python3_EXECUTABLE} -m venv venv COMMENT "Creating virtual environment for app1" ) # add custom target to build image for app1 add_custom_target(${IMAGE_TARGET} ALL WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/app1 COMMAND docker build -t image_app_1 . COMMENT "Building image for app1" DEPENDS ${VENV_TARGET} ) # add custom target to run container for app1 add_custom_target(container_app_1 COMMAND docker run --rm --name container_app_1 image_app_1 COMMENT "Running container for app1" DEPENDS ${IMAGE_TARGET} ) 
Enter fullscreen mode Exit fullscreen mode

When it comes to specifying dependencies for the container_app_1 target, you have 2 options:

  • you can add DEPENDS image_app_1 line, as I did above - this will make sure that the container being started, always bases on the newest version of the image, so it can be potentially re-built every single time when running the container
[ 33%] Creating virtual environment for app1 [ 33%] Built target venv_app_1 [ 66%] Building image for app1 [+] Building 1.1s (9/9) FINISHED => [internal] load build definition from Dockerfile => => transferring dockerfile: 162B => [internal] load metadata for docker.io/library/python:3.9-slim => [internal] load .dockerignore => => transferring context: 2B => [1/4] FROM docker.io/library/python:3.9-slim => [internal] load build context => => transferring context: 125.50kB => CACHED [2/4] WORKDIR /app => CACHED [3/4] COPY . /app => CACHED [4/4] RUN pip install --no-cache-dir -r requirements.txt => exporting to image => => exporting layers=> => writing image => => naming to docker.io/library/image_app_1 [ 66%] Built target image_app_1 [100%] Running container for app1 Hello from Python app1 [100%] Built target container_app_1 
Enter fullscreen mode Exit fullscreen mode
  • you can omit DEPENDS image_app_1 line - in such approach, the user running container_app_1 target is responsible for assuring that image_app_1 image already exists, but it will allow you to just run the container basing on whatever image version has been recently built
[100%] Running container for app1 Hello from Python app1 [100%] Built target container_app_1 
Enter fullscreen mode Exit fullscreen mode

I didn't add ALL to container_app_1 custom target because most likely you want to run the container on demand, not during every project build. To run the container, call:

cmake --build . --target container_app_1 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)