DEV Community

Cover image for Creating Base Docker Images
Kostas Kalafatis
Kostas Kalafatis

Posted on

Creating Base Docker Images

Creating your base Docker image is quite straightforward. Just as we used the docker commit command previously to create an image from a running container, we can also create an image from a system or server where our applications are originally hosted. However, it's crucial to keep the base image small and lightweight. Simply moving existing applications from traditional servers to Docker isn't enough.

If you're using a production server, the resulting image could end up quite large. For smaller virtual machines that seem ideal for a base image, you can follow these steps to create one. Similar to the docker commit command, this method works for any system you have access to.

Creating Custom Base Docker Images

Let's take the Docker image we're already using (basic-server) and transform it into a custom base image. This is a simple demo, but the same process works for bigger, more complex setups.

First, we need to start the container and connect to it at the same time. Here's how

docker run -it basic-server sh
Enter fullscreen mode Exit fullscreen mode

Use the docker image save command to create a backup of the repository:

docker save -o basebackup.tar basic-server
Enter fullscreen mode Exit fullscreen mode

Create a new image using the docker load command.

docker load --input .\basebackup.tar
Enter fullscreen mode Exit fullscreen mode

This example has demonstrated how to generate a base image from an existing running system or environment. However, if your goal is to create a minimal base image, the next section will explain how to utilize the scratch image.

The SCRATCH Image

The scratch image is Docker's secret weapon for building incredibly lean and efficient images. It's essentially a blank slate, devoid of any operating system or pre-installed software, making it the perfect starting point for running compiled binary applications like those written in Java or C++. These binaries are self-contained and don't rely on external libraries or dependencies, allowing them to run directly on the minimal environment provided by the scratch image.

By using the FROM scratch directive in your Dockerfile, you signal to Docker that you want to build an image with the absolute minimum footprint. This results in a container image that is remarkably small and lightweight, as it doesn't include any unnecessary overhead from an operating system or additional software layers.

Using the SCRATCH Image

In this example, we'll craft a simple C application to showcase how to build one of the most minimal Docker base images possible. While prior knowledge of C programming isn't required, this hands-on experience will demonstrate the process of creating a highly efficient and compact image using Docker's "scratch" image as our foundation. By leveraging the scratch image, we can eliminate any unnecessary dependencies and produce an image with a remarkably small footprint. Let's get started.

Try getting the scratch image using the docker pull command

docker pull scratch
Enter fullscreen mode Exit fullscreen mode

You will notice that you will get an error

Using default tag: latest
Error response from daemon: 'scratch' is a reserved name
Enter fullscreen mode Exit fullscreen mode

Create a C program that we will build into the image to use in our Dockerfile.

code christmas-tree.c
Enter fullscreen mode Exit fullscreen mode

Add the following C code:

#include <stdio.h>

int main() {
    int height = 12;
    int i, j, spaces;

    for (i = 1; i <= height; i++) {
        for (spaces = height - i; spaces > 0; spaces--) {
            printf(" ");
        }

        for (j = 1; j <= 2 * i - 1; j++) {
            printf("*");
        }
        printf("\n");
    }

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

This C program crafts a Christmas tree composed of asterisks based on user-specified height. It employs a series of nested loops to achieve this. The outer loop controls the number of rows, starting from the top of the tree and iterating downwards. For each row, an inner loop determines the number of spaces preceding the asterisks, ensuring the tree's triangular shape.

Another inner loop handles the actual printing of asterisks, with the number of asterisks increasing by two with each descending row. This creates the tree's wider base. A newline character is printed after each row, moving the cursor to the next line for the subsequent row's output. The result is a beautifully constructed Christmas tree in the console, ready to spread festive cheer!


Build the image form the command line by running the following command to build the C program

gcc -o christmas-tree christmas-tree.c
Enter fullscreen mode Exit fullscreen mode

Now lets create the Dockerfile. The Dockerfile will be pretty minimal but needs to start with FROM scratch. The rest of the file will add the C program and then run it

FROM scratch

ADD christmas-tree.exe /
CMD ["/christmas-tree.exe"]
Enter fullscreen mode Exit fullscreen mode

Build a new image. In this instance, call the image scratch-test using the following command

docker build -t scratch-test .
Enter fullscreen mode Exit fullscreen mode

Run the image from the command line

docker run scratch-test
Enter fullscreen mode Exit fullscreen mode

Run the docker images command for your new image

docker images scratch-test
Enter fullscreen mode Exit fullscreen mode

This will show some pretty impressive results with a size of 41.2kB in size

REPOSITORY     TAG       IMAGE ID       CREATED         SIZE
scratch-test   latest    817c50b9dbe6   3 minutes ago   41.2kB
Enter fullscreen mode Exit fullscreen mode

View the layers of the image using the docker history command

docker history scratch-test
Enter fullscreen mode Exit fullscreen mode

You will see a similar output to the following one

IMAGE          CREATED         CREATED BY                            SIZE      COMMENT
817c50b9dbe6   5 minutes ago   CMD ["./christmas-tree.exe"]          0B        buildkit.dockerfile.v0
<missing>      5 minutes ago   CMD ["ls"]                            0B        buildkit.dockerfile.v0
<missing>      5 minutes ago   ADD christmas-tree.exe / # buildkit   41.2kB    buildkit.dockerfile.v0
Enter fullscreen mode Exit fullscreen mode

Alright, the scratch image we whipped up in this exercise shows how to make a Docker image that's both super-lean and still gets the job done. It also proves that if you put some thought into your goals, you can easily speed up your builds and shrink those images down to size.

Conclusion

Creating a base Docker image is a straightforward process, but it's important to keep the image small and lightweight. You can create a custom base image from an existing running system or environment using the docker save and docker load commands.

Additionally, Docker's "scratch" image is a powerful tool for building minimal and efficient images, especially for compiled binary applications. By using the FROM scratch directive in your Dockerfile, you can create a container image with the absolute minimum footprint. This results in a remarkably small and lightweight image that eliminates unnecessary dependencies.

Overall, by following these techniques, you can optimize your Docker images for better performance and efficiency.

In the next post, let's take a breather from building for a moment and dive into the world of naming and tagging our Docker creations. After all, organizing our work is just as important as creating it

Top comments (1)

Collapse
 
tomasz_grygo_4488c140a423 profile image
Tomasz Grygo

Hi, Thank you for a great series.
I have some questions.

  1. you say "Just as we used the docker commit command previously to create an image from a running container". I don't remember any mention of using docker commit before. Is some part of the article missing?
  2. Example with a Christmas tree doesn't work for me. Compiled application works ok from my command line but when in a Docker image it does not print anything and exit code is 1. Thank you Tomasz