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
Use the docker image save
command to create a backup of the repository:
docker save -o basebackup.tar basic-server
Create a new image using the docker load
command.
docker load --input .\basebackup.tar
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
You will notice that you will get an error
Using default tag: latest
Error response from daemon: 'scratch' is a reserved name
Create a C program that we will build into the image to use in our Dockerfile.
code christmas-tree.c
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;
}
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
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"]
Build a new image. In this instance, call the image scratch-test
using the following command
docker build -t scratch-test .
Run the image from the command line
docker run scratch-test
Run the docker images
command for your new image
docker images scratch-test
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
View the layers of the image using the docker history
command
docker history scratch-test
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
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