Learning Docker
Introduction
Docker is an open source project that allows one to pack, ship, and run any application as a lightweight container. An analogy of Docker containers are shipping containers, which provide a standard and consistent way of shipping just about anything. The container includes everything that is needed for an application to run including the code, system tools, and the necessary dependencies. If you wanted to test an application, all you need to do is to download the Docker image and run it in a new container. No more compiling and installing missing dependencies!
The overview at https://docs.docker.com/ provides more information. For more a more hands-on approach, check out know Enough Docker to be Dangerous and this short workshop that I prepared for BioC Asia 2019.
This README was generated by GitHub Actions using the R Markdown file
readme.Rmd
, which was executed via the create_readme.sh
script.
Installing the Docker Engine
To get started, you will need to install the Docker Engine; check out this guide.
Checking your installation
To see if everything is working, try to obtain the Docker version.
docker --version
## Docker version 26.1.3, build b72abbb
And run the hello-world
image. (The --rm
parameter is used to
automatically remove the container when it exits.)
docker run --rm hello-world
## Unable to find image 'hello-world:latest' locally
## latest: Pulling from library/hello-world
## c1ec31eb5944: Pulling fs layer
## c1ec31eb5944: Verifying Checksum
## c1ec31eb5944: Download complete
## c1ec31eb5944: Pull complete
## Digest: sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdcec6
## Status: Downloaded newer image for hello-world:latest
##
## Hello from Docker!
## This message shows that your installation appears to be working correctly.
##
## To generate this message, Docker took the following steps:
## 1. The Docker client contacted the Docker daemon.
## 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
## (amd64)
## 3. The Docker daemon created a new container from that image which runs the
## executable that produces the output you are currently reading.
## 4. The Docker daemon streamed that output to the Docker client, which sent it
## to your terminal.
##
## To try something more ambitious, you can run an Ubuntu container with:
## $ docker run -it ubuntu bash
##
## Share images, automate workflows, and more with a free Docker ID:
## https://hub.docker.com/
##
## For more examples and ideas, visit:
## https://docs.docker.com/get-started/
Docker information
Get more version information.
docker version
## Client: Docker Engine - Community
## Version: 26.1.3
## API version: 1.45
## Go version: go1.21.10
## Git commit: b72abbb
## Built: Thu May 16 08:33:29 2024
## OS/Arch: linux/amd64
## Context: default
##
## Server: Docker Engine - Community
## Engine:
## Version: 26.1.3
## API version: 1.45 (minimum version 1.24)
## Go version: go1.21.10
## Git commit: 8e96db1
## Built: Thu May 16 08:33:29 2024
## OS/Arch: linux/amd64
## Experimental: false
## containerd:
## Version: 1.7.18
## GitCommit: ae71819c4f5e67bb4d5ae76a6b735f29cc25774e
## runc:
## Version: 1.7.18
## GitCommit: v1.1.13-0-g58aa920
## docker-init:
## Version: 0.19.0
## GitCommit: de40ad0
Even more information.
docker info
## Client: Docker Engine - Community
## Version: 26.1.3
## Context: default
## Debug Mode: false
## Plugins:
## buildx: Docker Buildx (Docker Inc.)
## Version: v0.15.1
## Path: /usr/libexec/docker/cli-plugins/docker-buildx
## compose: Docker Compose (Docker Inc.)
## Version: v2.27.1
## Path: /usr/libexec/docker/cli-plugins/docker-compose
##
## Server:
## Containers: 0
## Running: 0
## Paused: 0
## Stopped: 0
## Images: 16
## Server Version: 26.1.3
## Storage Driver: overlay2
## Backing Filesystem: extfs
## Supports d_type: true
## Using metacopy: false
## Native Overlay Diff: false
## userxattr: false
## Logging Driver: json-file
## Cgroup Driver: cgroupfs
## Cgroup Version: 2
## Plugins:
## Volume: local
## Network: bridge host ipvlan macvlan null overlay
## Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
## Swarm: inactive
## Runtimes: io.containerd.runc.v2 runc
## Default Runtime: runc
## Init Binary: docker-init
## containerd version: ae71819c4f5e67bb4d5ae76a6b735f29cc25774e
## runc version: v1.1.13-0-g58aa920
## init version: de40ad0
## Security Options:
## apparmor
## seccomp
## Profile: builtin
## cgroupns
## Kernel Version: 6.5.0-1023-azure
## Operating System: Ubuntu 22.04.4 LTS
## OSType: linux
## Architecture: x86_64
## CPUs: 4
## Total Memory: 15.61GiB
## Name: fv-az1393-226
## ID: e47e7669-35e6-4532-94ca-a2b577895d60
## Docker Root Dir: /var/lib/docker
## Debug Mode: false
## Username: githubactions
## Experimental: false
## Insecure Registries:
## 127.0.0.0/8
## Live Restore Enabled: false
Basics
The two guides linked in the introduction section provide some information on the basic commands but I’ll include some here as well. One of the main reasons I use Docker is for building tools. For this purpose, I use Docker like a virtual machine, where I can install whatever I want. This is important because I can do my testing in an isolated environment and not worry about affecting the main server. I like to use Ubuntu because it’s a popular Linux distribution and therefore whenever I run into a problem, chances are higher that someone else has had the same problem, asked a question on a forum, and received a solution.
Before we can run Ubuntu using Docker, we need an image. We can obtain
an Ubuntu image from the official Ubuntu image
repository from Docker Hub by running
docker pull
.
docker pull ubuntu:18.04
## 18.04: Pulling from library/ubuntu
## 7c457f213c76: Pulling fs layer
## 7c457f213c76: Verifying Checksum
## 7c457f213c76: Download complete
## 7c457f213c76: Pull complete
## Digest: sha256:152dc042452c496007f07ca9127571cb9c29697f42acbfad72324b2bb2e43c98
## Status: Downloaded newer image for ubuntu:18.04
## docker.io/library/ubuntu:18.04
To run Ubuntu using Docker, we use docker run
.
docker run --rm ubuntu:18.04 cat /etc/os-release
## NAME="Ubuntu"
## VERSION="18.04.6 LTS (Bionic Beaver)"
## ID=ubuntu
## ID_LIKE=debian
## PRETTY_NAME="Ubuntu 18.04.6 LTS"
## VERSION_ID="18.04"
## HOME_URL="https://www.ubuntu.com/"
## SUPPORT_URL="https://help.ubuntu.com/"
## BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
## PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
## VERSION_CODENAME=bionic
## UBUNTU_CODENAME=bionic
You can work interactively with the Ubuntu image by specifying the -it
option.
docker run --rm -it ubuntu:18:04 /bin/bash
You may have noticed that I keep using the --rm
option, which removes
the container once you quit. If you don’t use this option, the container
is saved up until the point that you exit; all changes you made, files
you created, etc. are saved. Why am I deleting all my changes? Because
there is a better (and more reproducible) way to make changes to the
system and that is by using a Dockerfile.
Start containers automatically
When hosting a service using Docker (such as running RStudio
Server),
it would be nice if the container automatically starts up again when the
server (and Docker) restarts. If you use --restart flag
with
docker run
, Docker will restart your
container
when your container has exited or when Docker restarts. The value of the
--restart
flag can be the following:
no
- do not automatically restart (default)on-failure[:max-retries]
- restarts if it exits due to an error (non-zero exit code) and the number of attempts is limited using themax-retries
optionalways
- always restarts the container; if it is manually stopped, it is restarted only when the Docker daemon restarts (or when the container is manually restarted)unless-stopped
- similar toalways
but when the container is stopped, it is not restarted even after the Docker daemon restarts.
docker run -d \
--restart always \
-p 8888:8787 \
-e PASSWORD=password \
-e USERID=$(id -u) \
-e GROUPID=$(id -g) \
rocker/rstudio:4.1.2
Dockerfile
A Dockerfile is a text file that contains instructions for building Docker images. A Dockerfile adheres to a specific format and set of instructions, which you can find at Dockerfile reference. There is also a Best practices guide for writing Dockerfiles.
A Docker image is made up of different layers and they act like snapshots. Each layer, or intermediate image, is created each time an instruction in the Dockerfile is executed. Each layer is assigned a unique hash and are cached by default. This means that you do not need to rebuild a layer again from scratch if it has not changed. Keep this in mind when creating a Dockerfile.
Some commonly used instructions include:
FROM
- Specifies the parent or base image to use for building an image and must be the first command in the file.COPY
- Copies files from the current directory (of where the Dockerfile is) to the image filesystem.RUN
- Executes a command inside the image.ADD
- Adds new files or directories from a source or URL to the image filesystem.ENTRYPOINT
- Makes the container run like an executable.CMD
- The default command or parameter/s for the container and can be used withENTRYPOINT
.WORKDIR
- Sets the working directory for the image. AnyCMD
,RUN
,COPY
, orENTRYPOINT
instruction after theWORKDIR
declaration will be executed in the context of the working directory.USER
- Changes the user
I have an example Dockerfile that uses the Ubuntu 18.04 image to build BWA, a popular short read alignment tool used in bioinformatics.
cat Dockerfile
## FROM ubuntu:18.04
##
## MAINTAINER Dave Tang <me@davetang.org>
##
## LABEL source="https://github.com/davetang/learning_docker/blob/main/Dockerfile"
##
## RUN apt-get clean all && \
## apt-get update && \
## apt-get upgrade -y && \
## apt-get install -y \
## build-essential \
## wget \
## zlib1g-dev && \
## apt-get clean all && \
## apt-get purge && \
## rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
##
## RUN mkdir /src && \
## cd /src && \
## wget https://github.com/lh3/bwa/releases/download/v0.7.17/bwa-0.7.17.tar.bz2 && \
## tar xjf bwa-0.7.17.tar.bz2 && \
## cd bwa-0.7.17 && \
## make && \
## mv bwa /usr/local/bin && \
## cd && rm -rf /src
##
## WORKDIR /work
##
## CMD ["bwa"]
ARG
To define variables in your Dockerfile use ARG name=value
. For
example, you can use ARG
to create a new variable that stores a
version number of a program. When a new version of the program is
released, you can simply change the ARG
and re-build your Dockerfile.
ARG star_ver=2.7.10a
RUN cd /usr/src && \
wget https://github.com/alexdobin/STAR/archive/refs/tags/${star_ver}.tar.gz && \
tar xzf ${star_ver}.tar.gz && \
rm ${star_ver}.tar.gz && \
cd STAR-${star_ver}/source && \
make STAR && \
cd /usr/local/bin && \
ln -s /usr/src/STAR-${star_ver}/source/STAR .
CMD
The CMD instruction in a Dockerfile does not execute anything at build time but specifies the intended command for the image; there can only be one CMD instruction in a Dockerfile and if you list more than one CMD then only the last CMD will take effect. The main purpose of a CMD is to provide defaults for an executing container.
COPY
The COPY
instruction copies new files or directories from <src>
and adds them
to the filesystem of the container at the path <dest>
. It has two
forms:
COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
Note the --chown
parameter, which can be used to set the ownership of
the copied files/directories. If this is not specified, the default
ownership is root
, which can be a problem.
For example in the RStudio Server
Dockerfile,
there are two COPY
instructions that set the ownership to the
rstudio
user.
COPY --chown=rstudio:rstudio rstudio/rstudio-prefs.json /home/rstudio/.config/rstudio
COPY --chown=rstudio:rstudio rstudio/.Rprofile /home/rstudio/
The two files that are copied are config files and therefore need to be
writable by rstudio
if settings are changed in RStudio Server.
Usually the root path of <src>
is set to the directory where the
Dockerfile exists. The example above is different because the RStudio
Server image is built by GitHub Actions, and the root path of <src>
is
the GitHub repository.
ENTRYPOINT
An ENTRYPOINT allows you to configure a container that will run as an executable. ENTRYPOINT has two forms:
- ENTRYPOINT [“executable”, “param1”, “param2”] (exec form, preferred)
- ENTRYPOINT command param1 param2 (shell form)
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
Use --entrypoint
to override ENTRYPOINT instruction.
docker run --entrypoint
Building an image
Use the build
subcommand to build Docker images and use the -f
parameter if your Dockerfile is named as something else otherwise Docker
will look for a file named Dockerfile
. The period at the end, tells
Docker to look in the current directory.
cat build.sh
## #!/usr/bin/env bash
##
## set -euo pipefail
##
## ver=0.7.17
##
## docker build -t davetang/bwa:${ver} .
You can push the built image to Docker Hub if you have an account. I have used my Docker Hub account name to name my Docker image.
# use -f to specify the Dockerfile to use
# the period indicates that the Dockerfile is in the current directory
docker build -f Dockerfile.base -t davetang/base .
# log into Docker Hub
docker login
# push to Docker Hub
docker push davetang/base
Renaming an image
The docker image tag
command will create a new tag, i.e. new image
name, that refers to an old image. It is not quite renaming but can be
considered renaming since you will have a new name for your image.
The usage is:
Usage: docker image tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
For example I have created a new tag for my RStudio Server image, so that I can easily push it to Quay.io.
docker image tag davetang/rstudio:4.2.2 quay.io/davetang31/rstudio:4.2.2
The original image davetang/rstudio:4.2.2
still exists, which is why
tagging is not quite renaming.
Running an image
docker run --rm davetang/bwa:0.7.17
## Unable to find image 'davetang/bwa:0.7.17' locally
## 0.7.17: Pulling from davetang/bwa
## feac53061382: Pulling fs layer
## 549f86662946: Pulling fs layer
## 5f22362f8660: Pulling fs layer
## 3836f06c7ac7: Pulling fs layer
## 3836f06c7ac7: Waiting
## 5f22362f8660: Verifying Checksum
## 5f22362f8660: Download complete
## feac53061382: Verifying Checksum
## feac53061382: Download complete
## 3836f06c7ac7: Verifying Checksum
## 3836f06c7ac7: Download complete
## 549f86662946: Verifying Checksum
## 549f86662946: Download complete
## feac53061382: Pull complete
## 549f86662946: Pull complete
## 5f22362f8660: Pull complete
## 3836f06c7ac7: Pull complete
## Digest: sha256:f0da4e206f549ed8c08f5558b111cb45677c4de6a3dc0f2f0569c648e8b27fc5
## Status: Downloaded newer image for davetang/bwa:0.7.17
##
## Program: bwa (alignment via Burrows-Wheeler transformation)
## Version: 0.7.17-r1188
## Contact: Heng Li <lh3@sanger.ac.uk>
##
## Usage: bwa <command> [options]
##
## Command: index index sequences in the FASTA format
## mem BWA-MEM algorithm
## fastmap identify super-maximal exact matches
## pemerge merge overlapping paired ends (EXPERIMENTAL)
## aln gapped/ungapped alignment
## samse generate alignment (single ended)
## sampe generate alignment (paired ended)
## bwasw BWA-SW for long queries
##
## shm manage indices in shared memory
## fa2pac convert FASTA to PAC format
## pac2bwt generate BWT from PAC
## pac2bwtgen alternative algorithm for generating BWT
## bwtupdate update .bwt to the new format
## bwt2sa generate SA from BWT and Occ
##
## Note: To use BWA, you need to first index the genome with `bwa index'.
## There are three alignment algorithms in BWA: `mem', `bwasw', and
## `aln/samse/sampe'. If you are not sure which to use, try `bwa mem'
## first. Please `man ./bwa.1' for the manual.
Setting environment variables
Create a new environment variable (ENV) using --env
.
docker run --rm --env YEAR=1984 busybox env
## Unable to find image 'busybox:latest' locally
## latest: Pulling from library/busybox
## ec562eabd705: Pulling fs layer
## ec562eabd705: Verifying Checksum
## ec562eabd705: Download complete
## ec562eabd705: Pull complete
## Digest: sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7
## Status: Downloaded newer image for busybox:latest
## PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
## HOSTNAME=6756bfb21218
## YEAR=1984
## HOME=/root
Two ENVs.
docker run --rm --env YEAR=1984 --env SEED=2049 busybox env
## PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
## HOSTNAME=63e709574457
## YEAR=1984
## SEED=2049
## HOME=/root
Or -e
for less typing.
docker run --rm -e YEAR=1984 -e SEED=2049 busybox env
## PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
## HOSTNAME=1ddc0f93c2d8
## YEAR=1984
## SEED=2049
## HOME=/root
Resource usage
To
restrict
CPU usage use --cpus=n
and use --memory=
to restrict the maximum
amount of memory the container can use.
We can confirm the limited CPU usage by running an endless while loop
and using docker stats
to confirm the CPU usage. Remember to use
docker stop
to stop the container after confirming the usage!
Restrict to 1 CPU.
# run in detached mode
docker run --rm -d --cpus=1 davetang/bwa:0.7.17 perl -le 'while(1){ }'
# check stats and use control+c to exit
docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
8cc20bcfa4f4 vigorous_khorana 100.59% 572KiB / 1.941GiB 0.03% 736B / 0B 0B / 0B 1
docker stop 8cc20bcfa4f4
Restrict to 1/2 CPU.
# run in detached mode
docker run --rm -d --cpus=0.5 davetang/bwa:0.7.17 perl -le 'while(1){ }'
# check stats and use control+c to exit
docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
af6e812a94da unruffled_liskov 50.49% 584KiB / 1.941GiB 0.03% 736B / 0B 0B / 0B 1
docker stop af6e812a94da
Copying files between host and container
Use docker cp
but I recommend mounting a volume to a Docker container
(see next section).
docker cp --help
Usage: docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
Copy files/folders between a container and the local filesystem
Options:
-L, --follow-link Always follow symbol link in SRC_PATH
--help Print usage
# find container name
docker ps -a
# create file to transfer
echo hi > hi.txt
docker cp hi.txt fee424ef6bf0:/root/
# start container
docker start -ai fee424ef6bf0
# inside container
cat /root/hi.txt
hi
# create file inside container
echo bye > /root/bye.txt
exit
# transfer file from container to host
docker cp fee424ef6bf0:/root/bye.txt .
cat bye.txt
bye
Sharing between host and container
Use the -v
flag to mount directories to a container so that you can
share files between the host and container.
In the example below, I am mounting data
from the current directory
(using the Unix command pwd
) to /work
in the container. I am working
from the root directory of this GitHub repository, which contains the
data
directory.
ls data
## README.md
## chrI.fa.gz
Any output written to /work
inside the container, will be accessible
inside data
on the host. The command below will create BWA index files
for data/chrI.fa.gz
.
docker run --rm -v $(pwd)/data:/work davetang/bwa:0.7.17 bwa index chrI.fa.gz
## [bwa_index] Pack FASTA... 0.15 sec
## [bwa_index] Construct BWT for the packed sequence...
## [bwa_index] 3.47 seconds elapse.
## [bwa_index] Update BWT... 0.07 sec
## [bwa_index] Pack forward-only FASTA... 0.11 sec
## [bwa_index] Construct SA from BWT and Occ... 1.28 sec
## [main] Version: 0.7.17-r1188
## [main] CMD: bwa index chrI.fa.gz
## [main] Real time: 5.121 sec; CPU: 5.093 sec
We can see the newly created index files.
ls -lrt data
## total 30436
## -rw-r--r-- 1 runner docker 194 Jul 12 02:52 README.md
## -rw-r--r-- 1 runner docker 4772981 Jul 12 02:52 chrI.fa.gz
## -rw-r--r-- 1 root root 15072516 Jul 12 02:56 chrI.fa.gz.bwt
## -rw-r--r-- 1 root root 3768110 Jul 12 02:56 chrI.fa.gz.pac
## -rw-r--r-- 1 root root 41 Jul 12 02:56 chrI.fa.gz.ann
## -rw-r--r-- 1 root root 13 Jul 12 02:56 chrI.fa.gz.amb
## -rw-r--r-- 1 root root 7536272 Jul 12 02:56 chrI.fa.gz.sa
However note that the generated files are owned by root
, which is
slightly annoying because unless we have root access, we need to start a
Docker container with the volume re-mounted to alter/delete the files.
File permissions
As seen above, files generated inside the container on a mounted volume
are owned by root
. This is because the default user inside a Docker
container is root
. In Linux, there is typically a root
user with the
UID and GID of 0; this user exists in the host Linux environment (where
the Docker engine is running) as well as inside the Docker container.
In the example below, the mounted volume is owned by UID 1211 and GID
1211 (in the host environment). This UID and GID does not exist in the
Docker container, thus the UID and GID are shown instead of a name like
root
. This is important to understand because to circumvent this file
permission issue, we need to create a user that matches the UID and GID
in the host environment.
ls -lrt
# total 2816
# -rw-r--r-- 1 1211 1211 1000015 Apr 27 02:00 ref.fa
# -rw-r--r-- 1 1211 1211 21478 Apr 27 02:00 l100_n100_d400_31_2.fq
# -rw-r--r-- 1 1211 1211 21478 Apr 27 02:00 l100_n100_d400_31_1.fq
# -rw-r--r-- 1 1211 1211 119 Apr 27 02:01 run.sh
# -rw-r--r-- 1 root root 1000072 Apr 27 02:03 ref.fa.bwt
# -rw-r--r-- 1 root root 250002 Apr 27 02:03 ref.fa.pac
# -rw-r--r-- 1 root root 40 Apr 27 02:03 ref.fa.ann
# -rw-r--r-- 1 root root 12 Apr 27 02:03 ref.fa.amb
# -rw-r--r-- 1 root root 500056 Apr 27 02:03 ref.fa.sa
# -rw-r--r-- 1 root root 56824 Apr 27 02:04 aln.sam
As mentioned already, having root
ownership is problematic because
when we are back in the host environment, we can’t modify these files.
To circumvent this, we can create a user that matches the host user by
passing three environmental variables from the host to the container.
docker run -it \
-v ~/my_data:/data \
-e MYUID=$(id -u) \
-e MYGID=$(id -g) \
-e ME=$(whoami) \
bwa /bin/bash
We use the environment variables and the following steps to create an identical user inside the container.
adduser --quiet --home /home/san/$ME --no-create-home --gecos "" --shell /bin/bash --disabled-password $ME
# optional: give yourself admin privileges
echo "%$ME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# update the IDs to those passed into Docker via environment variable
sed -i -e "s/1000:1000/$MYUID:$MYGID/g" /etc/passwd
sed -i -e "s/$ME:x:1000/$ME:x:$MYGID/" /etc/group
# su - as the user
exec su - $ME
# run BWA again, after you have deleted the old files as root
bwa index ref.fa
bwa mem ref.fa l100_n100_d400_31_1.fq l100_n100_d400_31_2.fq > aln.sam
# check output
ls -lrt
# total 2816
# -rw-r--r-- 1 dtang dtang 1000015 Apr 27 02:00 ref.fa
# -rw-r--r-- 1 dtang dtang 21478 Apr 27 02:00 l100_n100_d400_31_2.fq
# -rw-r--r-- 1 dtang dtang 21478 Apr 27 02:00 l100_n100_d400_31_1.fq
# -rw-r--r-- 1 dtang dtang 119 Apr 27 02:01 run.sh
# -rw-rw-r-- 1 dtang dtang 1000072 Apr 27 02:12 ref.fa.bwt
# -rw-rw-r-- 1 dtang dtang 250002 Apr 27 02:12 ref.fa.pac
# -rw-rw-r-- 1 dtang dtang 40 Apr 27 02:12 ref.fa.ann
# -rw-rw-r-- 1 dtang dtang 12 Apr 27 02:12 ref.fa.amb
# -rw-rw-r-- 1 dtang dtang 500056 Apr 27 02:12 ref.fa.sa
# -rw-rw-r-- 1 dtang dtang 56824 Apr 27 02:12 aln.sam
# exit container
exit
This time when you check the file permissions in the host environment, they should match your username.
ls -lrt ~/my_data
# total 2816
# -rw-r--r-- 1 dtang dtang 1000015 Apr 27 10:00 ref.fa
# -rw-r--r-- 1 dtang dtang 21478 Apr 27 10:00 l100_n100_d400_31_2.fq
# -rw-r--r-- 1 dtang dtang 21478 Apr 27 10:00 l100_n100_d400_31_1.fq
# -rw-r--r-- 1 dtang dtang 119 Apr 27 10:01 run.sh
# -rw-rw-r-- 1 dtang dtang 1000072 Apr 27 10:12 ref.fa.bwt
# -rw-rw-r-- 1 dtang dtang 250002 Apr 27 10:12 ref.fa.pac
# -rw-rw-r-- 1 dtang dtang 40 Apr 27 10:12 ref.fa.ann
# -rw-rw-r-- 1 dtang dtang 12 Apr 27 10:12 ref.fa.amb
# -rw-rw-r-- 1 dtang dtang 500056 Apr 27 10:12 ref.fa.sa
# -rw-rw-r-- 1 dtang dtang 56824 Apr 27 10:12 aln.sam
File Permissions 2
There is a -u
or --user
parameter that can be used with docker run
to run a container using a specific user. This is easier than creating a
new user.
In this example we run the touch
command as root
.
docker run -v $(pwd):/$(pwd) ubuntu:22.10 touch $(pwd)/test_root.txt
ls -lrt $(pwd)/test_root.txt
## Unable to find image 'ubuntu:22.10' locally
## 22.10: Pulling from library/ubuntu
## 3ad6ea492c35: Pulling fs layer
## 3ad6ea492c35: Verifying Checksum
## 3ad6ea492c35: Download complete
## 3ad6ea492c35: Pull complete
## Digest: sha256:e322f4808315c387868a9135beeb11435b5b83130a8599fd7d0014452c34f489
## Status: Downloaded newer image for ubuntu:22.10
## -rw-r--r-- 1 root root 0 Jul 12 02:56 /home/runner/work/learning_docker/learning_docker/test_root.txt
In this example, we run the command as a user with the same UID and GID;
the stat
command is used to get the UID and GID.
docker run -v $(pwd):/$(pwd) -u $(stat -c "%u:%g" $HOME) ubuntu:22.10 touch $(pwd)/test_mine.txt
ls -lrt $(pwd)/test_mine.txt
## -rw-r--r-- 1 runner docker 0 Jul 12 02:56 /home/runner/work/learning_docker/learning_docker/test_mine.txt
One issue with this method is that you may encounter the following warning (if running interactively):
groups: cannot find name for group ID 1000
I have no name!@ed9e8b6b7622:/$
This is because the user in your host environment does not exist in the container environment. As far as I am aware, this is not a problem; we just want to create files/directories with matching user and group IDs.
Read only
To mount a volume but with read-only permissions, append :ro
at the
end.
docker run --rm -v $(pwd):/work:ro davetang/bwa:0.7.17 touch test.txt
## touch: cannot touch 'test.txt': Read-only file system
Removing the image
Use docker rmi
to remove an image. You will need to remove any stopped
containers first before you can remove an image. Use docker ps -a
to
find stopped containers and docker rm
to remove these containers.
Let’s pull the busybox
image.
docker pull busybox
## Using default tag: latest
## latest: Pulling from library/busybox
## Digest: sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7
## Status: Image is up to date for busybox:latest
## docker.io/library/busybox:latest
Check out busybox
.
docker images busybox
## REPOSITORY TAG IMAGE ID CREATED SIZE
## busybox latest 65ad0d468eb1 14 months ago 4.26MB
Remove busybox
.
docker rmi busybox
## Untagged: busybox:latest
## Untagged: busybox@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7
## Deleted: sha256:65ad0d468eb1c558bf7f4e64e790f586e9eda649ee9f130cd0e835b292bbc5ac
## Deleted: sha256:d51af96cf93e225825efd484ea457f867cb2b967f7415b9a3b7e65a2f803838a
Committing changes
Generally, it is better to use a Dockerfile to manage your images in a documented and maintainable way but if you still want to commit changes to your container (like you would for Git), read on.
When you log out of a container, the changes made are still stored; type
docker ps -a
to see all containers and the latest changes. Use
docker commit
to commit your changes.
docker ps -a
# git style commit
# -a, --author= Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
# -m, --message= Commit message
docker commit -m 'Made change to blah' -a 'Dave Tang' <CONTAINER ID> <image>
# use docker history <image> to check history
docker history <image>
Access running container
To access a container that is already running, perhaps in the background
(using detached mode: docker run
with -d
) use docker ps
to find
the name of the container and then use docker exec
.
In the example below, my container name is rstudio_dtang
.
docker exec -it rstudio_dtang /bin/bash
Cleaning up exited containers
I typically use the --rm
flag with docker run
so that containers are
automatically removed after I exit them. However, if you don’t use
--rm
, by default a container’s file system persists even after the
container exits. For example:
docker run hello-world
##
## Hello from Docker!
## This message shows that your installation appears to be working correctly.
##
## To generate this message, Docker took the following steps:
## 1. The Docker client contacted the Docker daemon.
## 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
## (amd64)
## 3. The Docker daemon created a new container from that image which runs the
## executable that produces the output you are currently reading.
## 4. The Docker daemon streamed that output to the Docker client, which sent it
## to your terminal.
##
## To try something more ambitious, you can run an Ubuntu container with:
## $ docker run -it ubuntu bash
##
## Share images, automate workflows, and more with a free Docker ID:
## https://hub.docker.com/
##
## For more examples and ideas, visit:
## https://docs.docker.com/get-started/
Show all containers.
docker ps -a
## CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
## 58063b585386 hello-world "/hello" 1 second ago Exited (0) Less than a second ago compassionate_banach
## 89d682575b32 ubuntu:22.10 "touch /home/runner/…" 2 seconds ago Exited (0) 1 second ago gallant_stonebraker
## ca3de985b5b0 ubuntu:22.10 "touch /home/runner/…" 3 seconds ago Exited (0) 2 seconds ago naughty_raman
We can use a sub-shell to get all (-a
) container IDs (-q
) that have
exited (-f status=exited
) and then remove them (docker rm -v
).
docker rm -v $(docker ps -a -q -f status=exited)
## 58063b585386
## 89d682575b32
## ca3de985b5b0
Check to see if the container still exists.
docker ps -a
## CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
We can set this up as a Bash script so that we can easily remove exited
containers. In the Bash script -z
returns true if $exited
is empty,
i.e. no exited containers, so we will only run the command when
$exited
is not true.
cat clean_up_docker.sh
## #!/usr/bin/env bash
##
## set -euo pipefail
##
## exited=`docker ps -a -q -f status=exited`
##
## if [[ ! -z ${exited} ]]; then
## docker rm -v $(docker ps -a -q -f status=exited)
## fi
##
## exit 0
As I have mentioned, you can use the –rm parameter to automatically clean up the container and remove the file system when the container exits.
docker run --rm hello-world
##
## Hello from Docker!
## This message shows that your installation appears to be working correctly.
##
## To generate this message, Docker took the following steps:
## 1. The Docker client contacted the Docker daemon.
## 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
## (amd64)
## 3. The Docker daemon created a new container from that image which runs the
## executable that produces the output you are currently reading.
## 4. The Docker daemon streamed that output to the Docker client, which sent it
## to your terminal.
##
## To try something more ambitious, you can run an Ubuntu container with:
## $ docker run -it ubuntu bash
##
## Share images, automate workflows, and more with a free Docker ID:
## https://hub.docker.com/
##
## For more examples and ideas, visit:
## https://docs.docker.com/get-started/
No containers.
docker ps -a
## CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Installing Perl modules
Use cpanminus
.
apt-get install -y cpanminus
# install some Perl modules
cpanm Archive::Extract Archive::Zip DBD::mysql
Creating a data container
This guide on working with Docker data
volumes
provides a really nice introduction. Use docker create
to create a
data container; the -v
indicates the directory for the data container;
the --name data_container
indicates the name of the data container;
and ubuntu
is the image to be used for the container.
docker create -v /tmp --name data_container ubuntu
If we run a new Ubuntu container with the --volumes-from
flag, output
written to the /tmp
directory will be saved to the /tmp
directory of
the data_container
container.
docker run -it --volumes-from data_container ubuntu /bin/bash
R
Use images from The Rocker Project,
for example rocker/r-ver:4.3.0
.
docker run --rm rocker/r-ver:4.3.0
## Unable to find image 'rocker/r-ver:4.3.0' locally
## 4.3.0: Pulling from rocker/r-ver
## 3c645031de29: Pulling fs layer
## eb5ba85ece65: Pulling fs layer
## 336082e130a7: Pulling fs layer
## d6f516f66899: Pulling fs layer
## e7191ae70de7: Pulling fs layer
## d6f516f66899: Waiting
## e7191ae70de7: Waiting
## eb5ba85ece65: Verifying Checksum
## eb5ba85ece65: Download complete
## d6f516f66899: Verifying Checksum
## d6f516f66899: Download complete
## 3c645031de29: Verifying Checksum
## 3c645031de29: Download complete
## e7191ae70de7: Verifying Checksum
## e7191ae70de7: Download complete
## 3c645031de29: Pull complete
## eb5ba85ece65: Pull complete
## 336082e130a7: Verifying Checksum
## 336082e130a7: Download complete
## 336082e130a7: Pull complete
## d6f516f66899: Pull complete
## e7191ae70de7: Pull complete
## Digest: sha256:48fb09f63e1cbcc1b0ce3974a8f206bff0804b6921bb36dfa08eafa264dad542
## Status: Downloaded newer image for rocker/r-ver:4.3.0
##
## R version 4.3.0 (2023-04-21) -- "Already Tomorrow"
## Copyright (C) 2023 The R Foundation for Statistical Computing
## Platform: x86_64-pc-linux-gnu (64-bit)
##
## R is free software and comes with ABSOLUTELY NO WARRANTY.
## You are welcome to redistribute it under certain conditions.
## Type 'license()' or 'licence()' for distribution details.
##
## Natural language support but running in an English locale
##
## R is a collaborative project with many contributors.
## Type 'contributors()' for more information and
## 'citation()' on how to cite R or R packages in publications.
##
## Type 'demo()' for some demos, 'help()' for on-line help, or
## 'help.start()' for an HTML browser interface to help.
## Type 'q()' to quit R.
##
## >
Saving and transferring a Docker image
You should just share the Dockerfile used to create your image but if you need another way to save and share an image, see this post on Stack Overflow.
docker save -o <save image to path> <image name>
docker load -i <path to image tar file>
Here’s an example.
# save on Unix server
docker save -o davebox.tar davebox
# copy file to MacBook Pro
scp davetang@192.168.0.31:/home/davetang/davebox.tar .
docker load -i davebox.tar
93c22f563196: Loading layer [==================================================>] 134.6 MB/134.6 MB
...
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
davebox latest d38f27446445 10 days ago 3.46 GB
docker run davebox samtools
Program: samtools (Tools for alignments in the SAM format)
Version: 1.3 (using htslib 1.3)
Usage: samtools <command> [options]
...
Sharing your image
Docker Hub
Create an account on Docker Hub; my account
is davetang
. Use docker login
to login and use docker push
to push
to Docker Hub (run docker tag
first if you didn’t name your image in
the format of yourhubusername/newrepo
).
docker login
# create repo on Docker Hub then tag your image
docker tag bb38976d03cf yourhubusername/newrepo
# push
docker push yourhubusername/newrepo
Quay.io
Create an account on Quay.io; you can use Quay.io for free as stated in their plans:
Can I use Quay for free? Yes! We offer unlimited storage and serving of public repositories. We strongly believe in the open source community and will do what we can to help!
Use docker login
to login
and use the credentials you set up when you created an account on
Quay.io.
docker login quay.io
Quay.io images are prefixed with quay.io
, so I used docker image tag
to create a new tag of my RStudio Server image. (Unfortunately, the
username davetang
was taken on RedHat [possibly by me a long time
ago], so I have to use davetang31
on Quay.io.)
docker image tag davetang/rstudio:4.2.2 quay.io/davetang31/rstudio:4.2.2
Push to Quay.io.
docker push quay.io/davetang31/rstudio:4.2.2
GitHub Actions
login-action is used to automatically login to Docker Hub when using GitHub Actions. This allows images to be automatically built and pushed to Docker Hub. There is also support for Quay.io.
Tips
Tip from https://support.pawsey.org.au/documentation/display/US/Containers: each RUN, COPY, and ADD command in a Dockerfile generates another layer in the container thus increasing its size; use multi-line commands and clean up package manager caches to minimise image size:
RUN apt-get update \
&& apt-get install -y \
autoconf \
automake \
gcc \
g++ \
python \
python-dev \
&& apt-get clean all \
&& rm -rf /var/lib/apt/lists/*
I have found it handy to mount my current directory to the same path
inside a Docker container and to set it as the working
directory;
the directory will be automatically created inside the container if it
does not already exist. When the container starts up, I will
conveniently be in my current directory. In the command below I have
also added the -u
option, which sets the user to
<name|uid>[:<group|gid>]
.
docker run --rm -it -u $(stat -c "%u:%g" ${HOME}) -v $(pwd):$(pwd) -w $(pwd) davetang/build:1.1 /bin/bash
If you do not want to preface docker
with sudo
, create a Unix group
called docker
and add users to it. On some Linux distributions, the
system automatically creates this group when installing Docker Engine
using a package manager. In that case, there is no need for you to
manually create the group. Check /etc/group
to see if the docker
group exists.
cat /etc/group | grep docker
If the docker
group does not exist, create the group:
sudo groupadd docker
Add users to the group.
sudo usermod -aG docker $USER
The user will need to log out and log back in, before the changes take effect.
On Linux, Docker is installed in /var/lib/docker
.
docker info -f '{{ .DockerRootDir }}'
# /var/lib/docker
This may not be ideal depending on your partitioning. To change the
default root directory update the daemon configuration file; the default
location on Linux is /etc/docker/daemon.json
. This file may not exist,
so you need to create it.
The example below makes /home/docker
the Docker root directory; you
can use any directory you want but just make sure it exists.
cat /etc/docker/daemon.json
{
"data-root": "/home/docker"
}
Restart the Docker server (this will take a little time, since all the files will be copied to the new location) and then check the Docker root directory.
sudo systemctl restart docker
docker info -f '{{ .DockerRootDir}}'
/home/docker
Check out the new home!
sudo ls -1 /home/docker
buildkit
containers
engine-id
image
network
overlay2
plugins
runtimes
swarm
tmp
volumes
Use --progress=plain
to show container output, which is useful for
debugging!
docker build --progress=plain -t davetang/scanpy:3.11 .
For Apple laptops using the the M[123] chips, use
--platform linux/amd64
if that’s the architecture of the image.
docker run --rm --platform linux/amd64 -p 8787:8787 rocker/verse:4.4.1/
Useful links
- Post installation steps
- A quick introduction to Docker
- The BioDocker project; check out their Wiki, which has a lot of useful information
- The impact of Docker containers on the performance of genomic pipelines
- Learn enough Docker to be useful
- 10 things to avoid in Docker containers
- The Play with Docker classroom brings you labs and tutorials that help you get hands-on experience using Docker
- Shifter enables container images for HPC
- http://biocworkshops2019.bioconductor.org.s3-website-us-east-1.amazonaws.com/page/BioconductorOnContainers__Bioconductor_Containers_Workshop/
- Run the Docker daemon as a non-root user (Rootless mode)