Introduction
It’s been a while, but thank you for checking in again! In one of our previous tutorials, we built a basic Docker container using nothing more than an Alpine Linux image and some container notes. As we’ve seen in other tutorials, we can extend this to run a myriad of other programs within the container on startup. But what good is a container if it can’t be shared? To do so, we need to learn a few new things: how to save a container, how to share a container, and how to load a container. Let’s get started, shall we?
The Setup
As stated in the introduction, we’re going to keep this tutorial simple. The main flow will be something like this:
- Build a single Docker container on a light-weight Alpine Linux image
- Enable the Alpine image with Python (and pip)
- Create a simply Python file for the container to run on startup
- Build the container and export as an image
- Move the image to a different folder to emulate container sharing
- Unpack and run the “shared” container
Docker file
Since we’ve seen many of these steps before, I’ll defer the explanations to previous Docker Demo tutorials. For brevity, the purpose of each step is added as a comment above each command in the dockerfile
.
# Define base image of the container
FROM alpine:3.14 AS build
# Add notes to the container
LABEL version = "1.0"
LABEL description = "A more advanced Docker file!"
# Define Python environment / install python & pip
ENV PYTHONUNBUFFERED=1
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN pip3 install --no-cache --upgrade pip setuptools
# Copy our Python script from local to the container
COPY ./hello.py .
# Auto-run the Python script
CMD ["python","hello.py"]
Python file: hello.py
Similar to how we kept things simple with our dockerfile
, let’s go ahead and do the same thing with a very basic Python file. Here, the Python file only prints out a single line of text, “Greetings, friends!”. Keep in mind, the Python script file is expected to be in the same location as our Dockerfile!
print("Greetings, friends!")
Building the Docker container
So far, we’re still in familiar territory. Let’s go ahead and build our Docker container with commands we’ve already learned:
docker build --no-cache -t dockerfile:v0 .
[+] Building 14.6s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/alpine:3.14 0.4s
=> CACHED [1/5] FROM docker.io/library/alpine:3.14@sha256:4c869a63e1b7c0722fed1e402a6466610327c3b83bdddb94bd94fb71da7f638a 0.0s
=> [internal] load build context 0.1s
=> => transferring context: 29B 0.0s
=> [2/5] RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python 2.7s
=> [3/5] RUN python3 -m ensurepip 5.0s
=> [4/5] RUN pip3 install --no-cache --upgrade pip setuptools 5.7s
=> [5/5] COPY ./hello.py . 0.1s
=> exporting to image 0.5s
=> => exporting layers 0.4s
=> => writing image sha256:c6a7cb4e5423410574e3859e344cfb0f8b1a3f485fe49211e2ee969aecad1e1f 0.0s
=> => naming to docker.io/library/dockerfile:v0
Now that our container has been built into an image (dockerfile:v0
), we can run it in interactive mode (docker run -it dockerfile:v0
):
docker run -it dockerfile:v0
Greetings, friends!
The output here is no surprise, but it confirms that everything we’ve done so far works perfectly, and that we’re using the correct image name.
Exporting a Docker container
With the old stuff out of the way, on to our new topic: exporting a Docker container. Similar to what we’ve seen in previous tutorials, Docker has a very nice and simple syntax for achieving the image export: docker save
. In fact, the documentation states that not only can we cherry pick particular tags we want to export, but we can also choose the name of the export file, as well! The syntax to do so follows this format:
docker save -o <output_file_name>.tar <image_tag>
Since this exports as to a tar
archive format, we can even peek inside the image using Windows Explorer; doing so, however, is not terribly revealing. We are presented with a list of alpha-numerically named folders, a repositories
file and a manifest.json
file…neither of which are immediately readable.
With this in mind, let’s go ahead and export our new container, tagged dockerfile:v0
as a randomly named tar
archive file, littleduck.tar
. Although no output is shown, a new file (littleduck.tar
) is added to the same file directory as our dockerfile
.
PS C:\Desktop\docker demo> docker save -o littleduck.tar dockerfile:v0
PS C:\Desktop\docker demo>
Sharing the Docker image
If we open a Windows Explorer to the folder where our dockerfile
resides, we see that the size of the exported Docker image is ~48 MB. The size is mainly due to not only the Docker dependencies installed within our container, but also the size of the Python installation, and, to a lesser degree, the size of the Alpine operating system we used as the base image for our container.
The smallish file size means it can be easily migrated to a server, e-mailed to a colleague, or placed on DockerHub. However, if DockerHub if your preferred method of sharing your Docker images, you’ll be much better off using Git
-like commands… but we’ll cover this in another tutorial. For now, let’s just say a friend has e-mailed us the file, and we’ve saved it in another folder. Since my files for this demo are located in C:\Desktop\docker demos\
, I’ll copy the new container to a separate folder, C:\Downloads\
.
Within the Downloads
folder, I can press Shift
and right click, selecting “Open Powershell Window here”. Within Powershell, I can run two commands — the first verifies the existence of our new Docker image archive, and the second confirms that Docker is callable via the Docker
environment variable.
PS C:\Downloads> ls
Directory: C:\Downloads
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/23/2022 9:10 AM 50614894 littleduck.tar
PS C:\Downloads> docker
Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Options:
--config string Location of client config files (default
"C:\\Users\\aaron\\.docker")
Importing the Docker image
With the new Docker image verified within our “new” folder, we can use another straight-forward Docker command to load the file: docker load
. Since we are calling Powershell within the Downloads
folder, we can simply use the following syntax:
docker load -i <file_name>
Here, the -i
flag stands for --input
, and our file name is simply the direct path of our new .tar
file. Within the Downloads
folder, then, we can execute the load command, followed by our normal docker run -it <image_name>
command:
C:\Downloads>docker load -i .\littleduck.tar
Loaded image: dockerfile:v0
C:\Downloads>docker run -it dockerfile:v0
Greetings, friends!
Aha, success! The first output generated by the Windows terminal / Powershell shows the .tar
file was successfully unpacked, and our image, dockerfile:v0
was loaded into the Docker environment. From here, we further validate the image’s operation via the output string, “Greetings, friends!”
Conclusion
In this tutorial, we’ve really come full-circle. Starting from scratch, we’ve now built a Docker container with Python functionality, and saved the compiled image into an external file. We’ve probed the image file in Windows Explorer, and we’ve moved the file to an arbitrary location on our computer — a simulation of sharing the file with others. From there, we called basic Docker commands to successfully load and import the image. Lastly, we ran the image and demonstrated its proper output.
So where do we go from here? Again, it’s your choice! Feel free to drop a suggestion in the comments below and let me know what topics you’d like to see covered. Until then, thanks again for learning with me — we’re all in this together! If you’re enjoying the content, please feel free to Like, comment, and subscribe — see you next time!
Get new content delivered directly to your inbox.