Diving with the Whale III - Persisting Data & Dockerfiles
Table of contents
- Persisting Data
- TASK
- Here we are! Docker Files
- Docker File Anatomy - Commands
- FROM
- RUN
- LABEL <key=value>
- CMD [<"command">, <"argument-1">, ..]
- ADD <host file(s)(or)directory(or)URL*> <path in the image*>
- COPY <host file(s)(or)directory*> <path in the image*>
- ENV <variable*>
- EXPOSE <port*/protocol>
- ENTRYPOINT <command*>
- WORKDIR
- BUILD IT!
- TASK
- Advanced Topics
Remember when I said No persisting data with a bold font? well, I lied! ๐
Persisting Data
Containers are designed to be ephemeral and isolated from the host machine. This creates problems when data is involved.
But, there are two ways around it.
1. Mapping Volumes
Just the same as we mapped ports we can map directories from the host machine to a container directory, so anything that happens here happens there and vice versa.
Let's say we want to run the Ubuntu image and map its documents directory to a d
docker run -v :
if the container directory does not exist, it'll create one for you
after you run the command anything you put in this directory on the host machine appears in the container directory that's mapped to it, and vice versa!
pwd
# /home/ahmed
# create directory contained
mkdir contained
# docker run -v <host-directory>:<container-directory> <image>
docker run -it -v /home/ahmed/contained:/mnt/contained-vol ubuntu
# the proper way is to put it under /mnt/ but thats not really a rule
# -it to give us an interactive shell inside the container
# now check if the directory is actually there
cd /mnt/contained-vol/
# and WALAH!
# now let's create a file says "Hello From The CONTAINER Side" inside the container
echo "Hello From The CONTAINER Side" > hello.msg
# now let's head back to our host machine and check the message we got from inside this container
# _______________________________________________________________________________
# fire up a new terminal
# get inside the shared directory
cd /home/ahmed/contained/
ls
# hello.msg
# yaay it's there
# let's read it
cat hello.msg
# Hello From The CONTAINER Side
# NICE! let's say hi back
echo "Hi From The HOST Side!" > hi-back.msg
# let's head back to our container shell session to check if we got the message
# _______________________________________________________________________________
# get back to the container terminal
ls
# hello.msg hi-back.msg
# let's read it, shall we?
cat hi-back.msg
# Hi From The HOST Side!
Now what happens if we stop and remove the container? can you guess?
Wild Idea: The data will persist!
Let's do it
# exit your container shell session
exit
# display all running containers
docker ps
# no thing running righ not
# display all containers
docker ps -a
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# 043808ca3998 ubuntu "/bin/bash" 14 minutes ago Exited (0) 25 seconds ago gallant_pascal
# remove the container
docker rm gallant_pascal
# gallant_pascal
# display all containers again
docker ps -a
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# the container got removed
# now let's check our files in the contained directory
# get inside the shared directory
cd /home/ahmed/contained/
ls
# hello.msg hi-back.msg
# both of the files still exists!
# we managed to persist data from docker container!
2. Docker Volumes
the second solution is Docker Volumes, which is the preferred one
These are volume mounts that are managed Docker
CREATE
Creates a new volume with the given name
# docker volume create <name>
docker volume create contained
LS
Lists all the volumes on your system
docker volume ls
# DRIVER VOLUME NAME
# local contained
INSPECT
Gets info about the given volume
docker volume inspect contained
[
{
"CreatedAt": "2021-05-04T03:40:33+02:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/contained/_data",
"Name": "contained",
"Options": {},
"Scope": "local"
}
]
PRUNE
Removes all unused local volumes
docker volume prune
RM
Removes one or more volumes
docker volume rm contained
So how to attach a volume to a container?
# create a new-volume named contained
docker volume create contained
# docker run -mount type=volume,source=<volume_name>,target=<mount-point-inside-the-container> <image>
docker run -it --mount type=volume,source=contained,target=/mnt/mounted-vol ubuntu
# the proper way is to put it under /mnt/ but thats not really a rule
# -it to give us an interactive shell inside the container
# now check if the directory is actually there
cd /mnt/contained-vol/
# and WALAH!
# now let's create a file says "Hello From The CONTAINER Side" inside the container
echo "HELLO FROM THE CONTAINER, I'M ATTACHED TO THE VOLUME \"CONTAINED\"" > hello.msg
# now let's head back to our host machine and check the message we got from inside this container
# _______________________________________________________________________________
# fire up a new terminal
# get the mount point by inspecting the volume
docker volume inspect contained | grep -i "mountpoint"
# "Mountpoint": "/var/lib/docker/volumes/contained/_data"
# now we know where the shared data is
# let's get in there
cd /var/lib/docker/volumes/contained/_data
ls
# hello.msg
# read the file content
cat hello.msg
#HELLO FROM THE CONTAINER, I'M ATTACHED TO THE VOLUME "CONTAINED"
# NICE! let's say hi back
echo "Hi From The HOST Side!" > hi-back.msg
# let's head back to our container shell session to check if we got the message
# _______________________________________________________________________________
# get back to the container terminal
ls
# hello.msg hi-back.msg
# let's read it, shall we?
cat hi-back.msg
# Hi From The HOST Side!
TASK
DO THIS TASK USING THE TWO OPTIONS, VOLUME MAPPING AND DOCKER VOLUMES
setup a script to send a log file (create a cron tab for example or copy log files every 1 minute) to the mapped volume or the docker volume.
Here we are! Docker Files
Docker files are specific instructions on how to build the docker image, things like libraries, environments, system updates, project files, and everything Docker Engine needs to build up the Docker image.
Docker File Anatomy - Commands
Usually, the naming convention is Dockerfile or Dockerfile.ubuntu
FROM
MUST BE THE FIRST COMMAND IN THE DOCKERFILE
ONLY ONE FROM COMMAND
Specify the image to base your image on so you won't have to start from scratch. For example in the above Dockerfile, we're basing our image on a Python image that would have most of the libraries already installed and the environment is ready, so we don't need to do this from scratch!
FROM ubuntu:18.04
RUN
Runs Linux commands inside the container
For example, what is the first thing you do when you acquire a new Linux machine? YES, you update and upgrade the repository! so that's a thing you would normally want the docker image to run as a command
RUN sudo apt update
LABEL <key=value>
adding metadata like the author's data or info about the image
LABEL Author="Ahmed"
CMD [<"command">, <"argument-1">, ..]
ONLY ONE CMD COMMAND
Actually, I lied, you can have more than one CMD command, but only the last one will get executed!
Sets defaults for running a container
it tells the engine exactly how to run a container from this image if new commands are specified ( if any commands are specified it will overwrite the CMD command)
Only the last CMD will get
CMD ["python3", "app.py"]
ADD <host file(s)(or)directory(or)URL*> <path in the image*>
Copies files into the image from the host machine or remote URL into the image
ADD "https://www.notion.so/Diving-with-the-Whale-Docker-Day-III-97af6b7cda5249ab82df3656ea18e1da" /Documents
COPY <host file(s)(or)directory*> <path in the image*>
Copies files into the image from the host machine or remote URL into the image
COPY ~/myapp /
ENV <variable*>
Sets environment variables inside the image
ENV DATABASE_USER="dbuser"
# we can use or don't use the = sign
ENV DATABASE_HOST "host.com"
EXPOSE <port*/protocol>
Expose a port to the outer world!
for example, if we want to run a web app we need to expose port 80 for HTTP and perhaps port 443 for HTTPS
EXPOSE 80/tcp
ENTRYPOINT <command*>
Configure the container to run as an executable, only one ENTRYPOINT Is allowed.
ENTRYPOINT ["/bin/bash"]
WORKDIR
Sets the path of the working directory this changes the working directory to the path you're giving to it
WORKDIR /var/www/html
# now all the next commands (until the next RUN cd or WORKDIR) will be executed inside this dir
it's the same as
# RUN cd <path>
RUN cd /var/www/html
BUILD IT!
now from our Dockerfile, we want to build the image that we can run to get a running container, so how can we build a Docker file
# change your directory to the path where your Dockerfile is
cd project/
# run the build command
docker build -t flask-app:latest .
# -t to specify a tag for your image
# flask-app:latest is the tag we chose
# . is the path of the directory contains the docker file you want to build
TASK
Create a bash script that says hello from the container!
Then create a docker file based on Ubuntu, copy the script into the container and make the script run once you run the container.
Build the image!
Run and test your first image
Errors? debug and find the issue
Run again
Congratulations You Created Your First Docker Image! ๐
Advanced Topics
Subscribe to my newsletter
Read articles from Ahmed Ayman directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ahmed Ayman
Ahmed Ayman
Software Engineer @Bosta