C++ & Docker Hello World !

justanotherdev
AWS Tip
Published in
4 min readJan 29, 2022

--

Getting started with developing a cloud deployable C++ application, with an introduction to Docker and multistage builds, built using CMake.

Photo by Adam Chang on Unsplash

C++ has many great features and is supported on almost any system (if you spend enough time configuring the build process), but may not be as commonly seen in the cloud, due to complexity/development effort — Docker massively reduces part of this complexity.

This guide covers setting up a basic modern C++ 17 project (with CMake), and how to create a memory conscious Docker container, using the a multistage build.

Note: Before proceeding, please make sure you have an up to date version of CMake and Docker — any text editor for code will work! (I am using VSCode in this case).

Setting up the C++ Project

To begin with, let’s create a basic C++ project with CMake. First, create the project directory and cd into it, then create an empty main.cpp and CMakeLists.txt :

$ mkdir hello_cpp_docker
$ cd hello_cpp_docker
$ touch main.cpp
$ touch CMakeLists.txt

Then we can fill out the basic CMakeLists.txt with the following:

cmake_minimum_required(VERSION 3.9)project(hello_cpp_docker)# Set our project to use C++ 17
set(CMAKE_CXX_STANDARD 17)
# Set our source files as just the main.cpp
set(SOURCE_FILES main.cpp)
# Create our executable file from our source files
add_executable(cppdocker_run ${SOURCE_FILES})

As well as the main.cpp file with an empty main function:

#include <iostream>int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}

And that’s enough to build the C++ project for now, we will be doing the building itself within Docker for this guide, so you will not be running the build locally on your system.

Creating a Simple Docker Build

Now let’s create an empty Dockerfile in the root of the project:

$ touch Dockerfile

And fill the contents with:

FROM ubuntu:latestARG DEBIAN_FRONTEND=noninteractive # ignore user input required# Install required build dependencies
RUN apt-get -y update && apt-get install -y
RUN apt-get -y install g++ cmake git
COPY . .WORKDIR .# Run cmake configure & build process
RUN mkdir ./build
RUN cmake -B/build -S . -D CMAKE_BUILD_TYPE=Release
RUN cmake --build /build
# Launch built application
CMD ["./build/cppdocker_run"]

This will copy all of the project working directory to the container, and then attempt to run a cmake build in a Ubuntu image. Upon launching the container, it will attempt to run the binary built in the build stage, in this case it will print “Hello, World!” to the screen.

So let’s go ahead and build and run this with:

$ docker build -t hello_cpp_docker .
$ docker run -t hello_cpp_docker
Hello, World!

And that’s it, you now have your C++ application running in a Docker container!

Improving the Docker Build

Although this is a good start, in reality this build is still lacking in a major way; The container is bloated with all of the source code/unnecessary files/build tooling, which is not needed to run, so it’s size is much larger than needed.

This is where Docker’s multistage build process comes into play.

We can modify the Dockerfile to now look as follows:

# Name this initial stage "builder"
FROM ubuntu:latest AS builder
ARG DEBIAN_FRONTEND=noninteractiveRUN apt-get -y update && apt-get install -y
RUN apt-get -y install g++ cmake git
COPY . .WORKDIR .RUN mkdir ./build
RUN cmake -B/build -S . -D CMAKE_BUILD_TYPE=Release
RUN cmake --build /build
# Create a new stage for our output container
FROM ubuntu:latest
WORKDIR .# Copy the built binary over from previous stage
COPY --from=builder /build/cppdocker_run ./
# Run the binary in the new container
CMD ["./cppdocker_run"]

Now this has an additional stage, where we copy the built binary over from the builder stage. This now means that all of the source code and original build dependencies required (e.g g++ and cmake ) are not present on the new container.

Let’s go ahead and build this container:

$ docker build -t hello_cpp_docker_multistage .

And then we can have a look at the sizes of these containers:

$ docker images
hello_cpp_docker latest XXXXXXXXXXXX 3 minutes ago 450MB
hello_cpp_docker_multistage latest XXXXXXXXXXXX 2 minutes ago 72.8MB
....

We can see a drastic decrease in size from 450MB to 72.8MB (approx. 16% of the original size)!

Again we can run the container to check everything works:

$ docker run -t hello_cpp_docker_multistage
Hello, World!

The majority of this image size comes from the Ubuntu image. For the purpose of demonstration I have used the Ubuntu image as it will likely fit most of your development needs (and many of you will likely be familiar with it’s setup), however if you are looking to decrease the image size further, then the alpine image starts at 5MB! But you will need to set up a lot more yourself.

So in summary we have managed to:

  • Created a basic C++17 project with CMake.
  • Configure a standard Dockerfile to build and run your application on any Docker supported system.
  • Optimized the container image size using a multi-stage build process to reduce the image size by ~84%.

What’s Next?

Now that you have your C++ building and running with Docker, here are some ideas to take it a step further:

If you want to get started with a lightweight C++ HTTP server with Docker, checkout my other guide:

--

--

Software | Crypto | Data Science | DevOps | And a bit of everything else.