Creating your Own Base Image for Docker
Have you ever wondered what it's like to create a docker base image that users can import and build on? This tutorial will show you how to make your own Docker base images from scratch in two different approaches.
Hello World! Until now, you and I have relied on the Docker team's base images (ubuntu, alpine, and so on), but have you considered how we may construct our own base image from the ground up, rather than relying on someone else's base image? No more wait! In this post, I will guide you with two ways to create your own images from scratch: via File System and Scratch image
Before moving forward, I would like to clear some confusion between parent image, base image, and scratch image. If you already know that, feel free to skip this. Every Dockerfile starts with the FROM
directive which is used to tell the docker runtime which image to use as an underlying base image and perform operations on top of it.
A parent image is what tells the runtime to pull the image from the remote repository or use from the one already pulled. Most of the docker files start from this image instead of creating their own base image.
The base image is the one that has a special FROM scratch
directive. It will send the signal to the build system to not pull any image but to create from the start. You can say, the base image is the parent image of the parent image.

Creating from the File System
Docker image is nothing but information of files to be mounted via overlay filesystem that has all the available tools to run your application without installing any particular OS-level libraries like libc
and other standard things. To have these files, I have copied the libraries and binaries from /usr
directory to myimage
directory as shown below

/usr
directory to myimage
Now create a secret.txt
file with some random string. I am using openssl rand
function here. After you have done this, create the tar
archive of the image and save it into the tar file of any name.
Import the files using docker import
command which requires the tar archive file and the image name. Once this is done, you will see the sha256 hash digest of the image imported and docker images
output will show the entry of the same.

Now run the docker container of myimage:latest
image we have just imported as shown below

myimage
and reading contents of the secret fileThe default command executed by docker is defined in theENTRYPOINT
instruction. Since that was not defined while importing the image, you can provide it manually via--entrypoint </path/to/bin>
in docker run
Let's use our image as the parent image. In the following Dockerfile, I am using the filesystem from myimage:latest
image, creating a file with name mysecret.txt and appending another string to the secrets.txt. Lastly, the entrypoint is mentioned to spawn a shell so we don't have to provide it in the run command.
FROM myimage:latest
RUN touch mysecret.txt
RUN echo $(openssl rand -base64 32) >> /secret.txt
ENTRYPOINT ["/bin/sh"]
/bin/sh
Build the docker with different image name or tag or both. In this I am using the same image name myimage
but using different tag v2
. Let the runtime parse dockerfile and build the image.

Run the docker image with tag v2
. Now this time since the entrypoing is already defined, it is not required in the following command. However, docker still provides you an option to override it with the --entrypoint
option.

Creating from Scratch
The docker team has already noticed the need that users might require to create their own images. Keeping that in mind, they have already created a special base image known as scratch
and released on the Docker Hub
Despite being hosted on the public repository, you cannot pull it and save it to the local registry but provide it in the Dockerfile FROM
instruction which will trigger a signal in the build process that next coming file as the first one.
// file main.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s <name>\n", argv[0]);
return 0x1;
}
printf("Hello %s!\n", argv[1]);
return 0x0;
}
Compile the above code using GCC in the Linux system using the following command
gcc -o hello main.c -static
Since the base image has no other libraries required by the binary, we have used -static
flag in the GCC to tell the compiler that embed all the libraries in the binary (static linking)
Now create a Dockerfile in the current directory with the following instructions
FROM scratch
ADD hello /
ENTRYPOINT ["/hello"]
You can see the build succeeded in a very short time and the size of the image is less than the size of the binary created by the GCC build. This is because of the compressed image size and also has some metadata with it.

When you will export the image using docker save
and then check for the file systems, you will find only 1. Extract it, you will see that there is the same file that we have added from the local file system.

Now let's run the docker image and test whether our image is working as expected or not.
