# Getting started with the C/C++ Chainguard Containers

URL: https://chainguard-docs-preview-git-fix-learning-lab-jsonld.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/c.md
Last Modified: September 28, 2026
Tags: Chainguard Containers

Learn how to compile and run C/C++ applications using Chainguard's security-hardened containers with minimal CVEs and optimized runtime environments


Chainguard provides security-hardened container images for C and C++ development, offering minimal runtime environments with significantly reduced vulnerabilities compared to traditional base images. Built on Chainguard's own OS, these containers enable more secure deployment of compiled programs through purpose-built images for different linking scenarios. This guide demonstrates three approaches to compiling and running C/C++ applications using Chainguard's specialized containers.

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 a container for your compiled programs](/chainguard/containers/concepts/choosing-a-base-container/).

**What is distroless?**


Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.





**What is Wolfi?**


[Wolfi](https://github.com/wolfi-dev/) is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.




**What are multi-stage builds?**


<p><a href="https://docs.docker.com/build/building/multi-stage/">Multi-stage builds</a> are a Docker feature that allow you to use multiple <code>FROM</code> statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.</p>

<p>This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.</p>

<p>Chainguard's container images are designed with this pattern in mind. Most have a <a href="/chainguard/containers/concepts/container-variants/"><code>:latest-dev</code> development variant</a> suited for use as a builder stage, and a corresponding <code>:latest</code> (or <code>-slim</code>) standard image for the distroless runtime. For example, a Go application can be compiled in the <code>go:latest-dev</code> builder stage and its binary copied into a <code>static</code> or <code>glibc-dynamic</code> runtime image — with no Go toolchain in the final container.</p>




**Chainguard Containers**


[Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.




## Video

The content in this article is also available as a video.

[Watch the video on YouTube](https://www.youtube.com/watch?v=g7fCIRJ8_pE)


## Prerequisites

To follow along with this guide, you will need to have [Docker Engine](https://docs.docker.com/engine/install/) and `gcc`, the [GNU Compiler Collection](https://gcc.gnu.org/), installed on your machine. You can find the code and Dockerfiles used in our [Containers demos GitHub repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/c).

## Example 1 --- Minimal C Chainguard Container

### Step 1: Setting up a demo application

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.

```sh
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:

```sh
nano hello.c
```

Inside of your `hello.c` file, add in the following C code which will execute a "Hello, world!" application.

```C
/* 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`.

```sh
gcc -Wall -o hello hello.c
```

Once your program has compiled, you can run it with the following command:

```sh
./hello
```

This will return the "Hello, world!" program output in your terminal if the program executed successfully.

```Output
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.

### Step 2: Creating the Dockerfile

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.

```sh
nano Dockerfile1
```

This Dockerfile will do the following:

1. Use the `gcc-glibc:latest` Chainguard Container as the base image;
2. Create and set the current working directory to `/home/build`;
3. Copy the `hello.c` program code to the current directory;
4. Compile the program and name it `hello`;
5. Copy the compiled binary to `/usr/bin`;
6. Set the image to run as a non-root user; and,
7. Execute the compiled binary when the container is started.

```Dockerfile
# 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.

```sh
docker build -f Dockerfile1 -t example1:latest .
```

With your container image built, you can now run it with the following command.

```sh
docker run --name example1 example1:latest
```

You will see output in your terminal identical to that of the binary you compiled locally.

```Output
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.

## Example 2 --- Multi-stage build for C applications

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.

### Creating the Dockerfile

Create a new Dockerfile called `Dockerfile2`.

```sh
nano Dockerfile2
```

This time, the Dockerfile will do the following:

1. Use the `gcc-glibc` Chainguard Container as the builder stage;
2. Create and set the current working directory to `/home/build`;
3. Copy your example `hello.c` program code to the current directory;
4. Compile the program using `gcc` and name it `hello`;
5. Begin a new stage using the `glibc-dynamic` Chainguard Container;
6. Copy the compiled binary to `/usr/bin` from the builder stage;
7. Set the image to run as a non-root user; and,
8. Execute your binary from the `glibc-dynamic` image when the container is started.

```Dockerfile
# 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.

```sh
docker build -f Dockerfile2 -t example2:latest .
```

With your image built, you can now run it with the following command.

```sh
docker run --name example2 example2:latest
```

You will see output in your terminal identical to that of the previous example.

```Output
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.

## Example 3 --- Multi-stage build for C++ applications

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.

### Step 1: Setting up a demo application

In your terminal, create a new file called `hello.cpp`.

```sh
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++.

```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`.

```sh
g++ -Wall -o hello hello.cpp
```

Now you can test your compiled binary.

```sh
./hello
```

You will see the following output in your terminal.

```Output
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.

### Step 2: Creating the Dockerfile

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`.

```sh
nano Dockerfile3
```

This Dockerfile will do the following:

1. Use the `gcc-glibc` Chainguard Container as the builder stage;
2. Create and set the current working directory to `/home/build`;
3. Copy your example `hello.cpp` program code to the current directory;
4. Compile the program using `g++` and name it `hello`;
5. Begin a new stage using the `glibc-dynamic` Chainguard Container;
6. Copy the compiled binary to `/usr/bin` from the builder stage;
7. Set the image to run as a non-root user; and,
8. Execute your binary from the `glibc-dynamic` image when the container is started.

```Dockerfile
# 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.

```sh
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.

```Output
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.

## Clean up

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.

```sh
docker container rm example1 example2 example3
```

Then, you can remove their associated container image builds as well:

```sh
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.

## Advanced usage

<p>If your project requires a more specific set of packages that aren't included within the general-purpose C/C&#43;&#43; Chainguard Container, you'll first need to check if the package you want is already available on the <a href="https://github.com/wolfi-dev/os" title="Wolfi on GitHub">wolfi-os repository</a>. </p>

<blockquote>
    <p><b>Note</b>: 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 <code>apk add</code> you need to use the <code>USER root</code> directive.</p>
</blockquote>

<p>If the package is available, you can use the <a href="https://images.chainguard.dev/directory/image/wolfi-base/overview" title="Overview of wolfi-base image">wolfi-base</a> image in a Dockerfile and install what you need with <code>apk</code>, then use the resulting image as the base for your app.
For a worked example, see the <a href="/chainguard/containers/using-and-deploying/using-containers/#using-the-wolfi-base-container-image-within-dockerfiles">Using the wolfi-base container image within Dockerfiles</a> section of our <a href="/chainguard/containers/using-and-deploying/using-containers/" title="How to use Chainguard Containers">guide to using Chainguard Containers</a>.
</p>

<p>If the packages you need are not available, you can build your own apks using <a href="/open-source/build-tools/melange/">melange</a>. To get started, see <a href="/open-source/build-tools/melange/getting-started-with-melange/">Getting started with melange</a>.</p>


