Make your own Docker base image from scratch
The main advantage of Docker over any other containerization technology is that it’s not just a substitute for virtualization but is aimed at developers for customizing it as per their needs. If you need to completely control the contents of your image or want to keep the docker image size as small as possible, you might need to create a base image instead of using an existing parent image from the docker hub.
We will start this article by covering differences between parent and base images, why to create your own base image, create a simple parent image using scratch, followed by a step-by-step guide to create an Ubuntu Docker base image and install packages to wrap it up into a self-contained Docker image.
Parent Image vs Base Image
A parent image is the image that your image is based on. It refers to the contents of the FROM directive in the Dockerfile. All subsequent commands in the Dockerfile modify this parent image.
A base image has no parent image specified in its Dockerfile. In simple terms, a base image is an empty first layer, which allows you to build your Docker images ‘FROM scratch’.
Why create your own Base Image
- The parent image you’re using might have some security vulnerabilities or policy check violations that would be inherited in your image.
- You download many megabytes as you use preconfigured containers. A simple Ubuntu container easily exceeds 200MB and as packages are installed on top of it, the size further increases.
- Having unnecessary packages in the Docker images not just bloats the size of the image, but also increases the attack surface of the container. All the Docker images must be carefully examined for installed packages, and only the packages required for the functioning of the service(s) in the container must be included. One can start building their Docker images by using minimalistic base images like Alpine, BusyBox, or build one of their own.
Create a simple parent image using scratch
- You can use Docker’s reserved, minimal image, “scratch”, as a starting point for building containers. Using the “scratch image” signals to the build process that you want the next command in the Dockerfile to be the first filesystem layer in your image.
- While scratch appears in Docker’s repository on the hub, you can’t pull it, run it, or tag any image with the name scratch. Instead, you can refer to it in your Dockerfile. For example, to create a minimal container using scratch the following should be used as the first line of your Dockerfile: FROM scratch
- The scratch image is perfect. It is elegant, small and fast. It does not contain any bugs, security leaks, slow code or technical debt. And that is because it is basically empty. Except for a bit of metadata added by Docker. It is the smallest possible Docker image
Building Ubuntu Base Image
Note: This method is applicable to create docker base images of all Ubuntu versions.
Prerequisites -
- Debootstrap: We will install and use the debootstrap script to create the docker base image. To know more about debootstrap visit https://wiki.debian.org/Debootstrap.
- Ubuntu Release Code Name: To create the docker image we need ‘Ubuntu release code name’ for eg. “Bionic Beaver” or “Xenial Xerus”. You can find Ubuntu release code name from here — https://wiki.ubuntu.com/Releases.
We are using Ubuntu 16.04 LTS Desktop for creating a base image. Hence, all commands are for Debian/Ubuntu system.
- Either log in as root or become superuser by using the command
$ sudo su - Create a directory for docker base image and change the current working directory after creating it.
$ mkdir -p /opt/docker_base_images
$ cd /opt/docker_base_images - Install debootstrap
$ apt install debootstrap - Run debootstrap — Here we are creating Ubuntu 18.04 LTS docker base image. For this, we will use the Ubuntu release code name that is ‘Bionic’.
$ debootstrap bionic bionic > /dev/null
This command will take some time to finish. After the command gets completed, you will see the directory relevant to the Ubuntu code name you used. In our case, a directory called ‘bionic’ is created. - Explore the newly created Docker image directory. You can see the release details using the command:
$ cat bionic/etc/lsb-release - Now import the docker image in the local system.
$ sudo tar -C bionic -c . | docker import — bionic - Now list docker images, you can easily find your freshly imported docker image.
$ docker images - Verify the new docker image — Run this command to verify the Ubuntu docker image release version.
$ docker run bionic cat /etc/lsb-release
Now you have a Docker image ready to use just like any other image. You can choose to upload it to the Docker hub and use this image for creating docker containers directly pulling from your own Docker Hub repository.
Install packages
Let’s install python and python-pip packages in our fresh container.
- Before that we will have to start the container and run the bash terminal in our container using the following commands:
$ docker start bionic
$ docker exec -it bionic bash - You can easily install Python 3.6 with the following commands:
# sudo apt-get update
# sudo apt-get install python3.6
But while installing pip you might get the “Unable to locate package” error. This is because the universe repository which contains the python-pip package might be disabled.
- If you have software-properties-common installed, You can use this command to add universe category to your sources file:
# sudo add-apt-repository universe - However, if you have to add it manually or you don’t have the add-apt-repository command available to run then follow these instructions:
Open /etc/apt/sources.list using an editor, for example nano:
# sudo nano /etc/apt/sources.list
then add universe at the end of each line, like this:
deb http://archive.ubuntu.com/ubuntu bionic main universe
deb http://archive.ubuntu.com/ubuntu bionic-security main universe
deb http://archive.ubuntu.com/ubuntu bionic-updates main universe
Press Ctrl+o to save the file. Press Ctrl+x to quit nano. - then run:
# sudo apt update - and finally:
# sudo apt install python-pip
With this, we have completed building an Ubuntu Docker base image of our own and installed some useful packages on top of it along with learning some basics of Docker base/parent images and best practices for using them.