Using CVE Visualizations
Getting started with the CVE Visualization feature.
C and its derivative, C++, are two widely adopted compiled languages. Chainguard offers a variety of minimal, low-CVE container images built on the Wolfi un-distro which are suitable for deploying C-based compiled programs. In this guide, you will explore three ways you can use Chainguard Containers to compile and run a C-based binary.
The container image with which you choose to run your compiled program depends on the nature of your binaries. Static binaries can be executed in the minimal static
Chainguard Container, while dynamically linked binaries can be run in the glibc-dynamic
Container. For this demonstration, you will first compile a C binary using the gcc-glibc
Chainguard Container, and then learn how to use a multi-stage build to run the resulting binary in the glibc-dynamic
image. You’ll also cover an example showing the multi-stage build process for the C++ programming language. To learn more about the differences between these container images, read our article on Choosing an Container for your Compiled Programs.
The content in this article is also available as a video.
To follow along with this guide, you will need to have Docker Engine and gcc
, the GNU Compiler Collection, installed on your machine. You can find the code and Dockerfiles used in our Containers demos GitHub repository.
To start, let’s create a demo C application to run in your container. First you will create a folder to contain your demo files. The following command will create a new directory cguide
and navigate to it.
mkdir -p ~/cguide && cd ~/cguide
Within this directory, you will create a file to hold the code for your first program. Use the text editor of your choice to begin editing a new file named hello.c
. We will use nano
as an example:
nano hello.c
Inside of your hello.c
file, add in the following C code which will execute a “Hello, world!” application.
/* Chainguard Academy (edu.chainguard.dev)
* Getting Started with the C/C++ Chainguard Containers
* Examples 1 & 2 - C
*/
#include <stdio.h>
// Main Function
int main(){
printf("Hello, world!\n");
printf("I am a demo from the Chainguard Academy.\n");
printf("My code was written in C.\n");
return 0;
}
When you are done editing the file, save and close it. If you used nano
, you can do so by pressing CTRL + X
, Y
, and then ENTER
.
Now, let’s compile this file with gcc
. This command uses the -Wall
flag to display compiler errors and warnings, if any occur, and includes the -o
flag to rename your executable to hello
.
gcc -Wall -o hello hello.c
Once your program has compiled, you can run it with the following command:
./hello
This will return the “Hello, world!” program output in your terminal if the program executed successfully.
Hello, world!
I am a demo from the Chainguard Academy.
My code was written in C.
Now that you have successfully tested your example program locally, next, you will compile and run it from inside of a container image.
An advantage of choosing to run your code inside of containerized environments is portability. In the previous step, gcc
compiled the binary to run on your machine. However, if you were to run this binary on a different operating system, it likely will fail to execute properly. Using a container ensures that your program will run on any machine as the containerized environment will be consistent across platforms.
Let us begin by creating a Dockerfile called Dockerfile1
for your container image.
nano Dockerfile1
This Dockerfile will do the following:
gcc-glibc:latest
Chainguard Container as the base image;/home/build
;hello.c
program code to the current directory;hello
;/usr/bin
;# Example 1 - Single Stage Build for C
FROM cgr.dev/chainguard/gcc-glibc:latest
RUN ["mkdir", "/home/build"]
WORKDIR /home/build
COPY hello.c ./
RUN ["gcc", "-Wall", "-o", "hello", "hello.c"]
RUN ["cp", "hello", "/usr/bin/hello"]
USER 65532
ENTRYPOINT ["/usr/bin/hello"]
Add this text to your Dockerfile, save, and close it.
Next, use the Dockerfile you just created to build a container image named example1
by running the following command. The -f
flag specifies the Dockerfile which you are using to build from, and the -t
flag will tag your image with a meaningful name.
docker build -f Dockerfile1 -t example1:latest .
With your container image built, you can now run it with the following command.
docker run --name example1 example1:latest
You will see output in your terminal identical to that of the binary you compiled locally.
Hello, world!
I am a demo from the Chainguard Academy.
My code was written in C.
In the next example, we will look at an alternative way to run your binary using a multi-stage build.
In our first example, you successfully compiled and executed your C binary in the gcc-glibc
image. To go a step further, you can use a multi-stage build, allowing you to compile your program in one image and execute it in another image.
A multi-stage build gives you more control over your final image, as you can transfer your program to an image with a smaller footprint after build time to reduce your program’s attack surface. The glibc-dynamic
image, which you will use as your second stage in the build, does not contain gcc
. Because of this, a malicious binary could not be compiled by an attacker tampering with the image.
Create a new Dockerfile called Dockerfile2
.
nano Dockerfile2
This time, the Dockerfile will do the following:
gcc-glibc
Chainguard Container as the builder stage;/home/build
;hello.c
program code to the current directory;gcc
and name it hello
;glibc-dynamic
Chainguard Container;/usr/bin
from the builder stage;glibc-dynamic
image when the container is started.# Example 2 - Multi-Stage Build for C
FROM cgr.dev/chainguard/gcc-glibc:latest AS builder
RUN ["mkdir", "/home/build"]
WORKDIR /home/build
COPY hello.c ./
RUN ["gcc", "-Wall", "-o", "hello", "hello.c"]
FROM cgr.dev/chainguard/glibc-dynamic:latest
COPY --from=builder /home/build/hello /usr/bin/
USER 65532
ENTRYPOINT ["/usr/bin/hello"]
When you are finished editing your Dockerfile, save and close it.
With the new Dockerfile created, you can build the image. Execute the following command in your terminal to build your multi-stage image.
docker build -f Dockerfile2 -t example2:latest .
With your image built, you can now run it with the following command.
docker run --name example2 example2:latest
You will see output in your terminal identical to that of the previous example.
Hello, world!
I am a demo from the Chainguard Academy.
My code was written in C.
Having your program execute from a smaller container image with less packages reduces your potential attack surface, making it a more secure approach for production-facing builds.
So far, our demonstrations have featured a program coded in C. A similar image building process applies to binaries compiled for the C++ programming language.
In your terminal, create a new file called hello.cpp
.
nano hello.cpp
Add the following C++ code to the file you just created. This code will display a greeting specifying that it was written in C++.
/* Chainguard Academy (edu.chainguard.dev)
* Getting Started with the C/C++ Chainguard Containers
* Example 3 - C++
*/
#include <iostream>
using namespace std;
// Main Function
int main(){
cout << "Hello, world!\n";
cout << "I am a demo from the Chainguard Academy.\n";
cout << "My code was written in C++.\n";
return 0;
}
When you are done editing your file, save and close it.
You can now compile your C++ program using g++
. Execute the following command in your terminal to compile the program. The command will display any compiler warnings or errors and will name the resultant binary hello
.
g++ -Wall -o hello hello.cpp
Now you can test your compiled binary.
./hello
You will see the following output in your terminal.
Hello, world!
I am a demo from the Chainguard Academy.
My code was written in C++.
Now that you have confirmed that your C++ program executes, you are ready to build it inside of a container image.
With a working C++ example, you can compile and run our program using a multi-stage build. With the text editor of your choice, create a new file named Dockerfile3
.
nano Dockerfile3
This Dockerfile will do the following:
gcc-glibc
Chainguard Container as the builder stage;/home/build
;hello.cpp
program code to the current directory;g++
and name it hello
;glibc-dynamic
Chainguard Container;/usr/bin
from the builder stage;glibc-dynamic
image when the container is started.# Example 3 - Multi-Stage Build for C++
FROM cgr.dev/chainguard/gcc-glibc:latest AS builder
RUN ["mkdir", "/home/build"]
WORKDIR /home/build
COPY hello.cpp ./
RUN ["g++", "-Wall", "-o", "hello", "hello.cpp"]
FROM cgr.dev/chainguard/glibc-dynamic:latest
COPY --from=builder /home/build/hello /usr/bin/
USER 65532
ENTRYPOINT ["/usr/bin/hello"]
When you are finished editing your Dockerfile, save and close it.
With your new Dockerfile created, you can build the container image. Execute the following command in your terminal to build your multi-stage C++ image.
docker build -f Dockerfile3 -t example3:latest .
With your image built, you can now run it with the following command.
docker run --name example3 example3:latest
You will see output in your terminal identical to that of the C++ binary you compiled locally.
Hello, world!
I am a demo from the Chainguard Academy.
My code was written in C++.
With that, you have successfully performed a multi-stage image build for both C and C++ programs.
After completing the previous examples, you will have containers, images, and files remaining on your local machine. This section will show you how to remove these artifacts.
You can remove the containers you built by executing the following command.
docker container rm example1 example2 example3
Then, you can remove their associated container image builds as well:
docker image rm example1:latest example2:latest example3:latest
To remove the directory containing your Dockerfiles, binaries, and program code, run the following command:
rm -r ~/cguide
Following these commands, all artifacts introduced in this guide will now be removed from your machine.
If your project requires a more specific set of packages that aren't included within the general-purpose C/C++ Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.
Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with
apk install
you need to use theUSER root
directive.
If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk
, then use the resulting image as base for your app.
Check the "Using the wolfi-base Container" section of our images quickstart guide for more information.
If the packages you need are not available, you can build your own apks using melange. Please refer to this guide for more information.
Last updated: 2025-03-21 19:37