/var/run/docker.sock
)export DOCKER_HOST=...
to talk to remote engine listening on TCP portdocker
group to allow non-root users access to the docker
UNIX socketdocker image <command>
and container handling through
docker container <command>
. There are legacy commands that still can be
used, e. g. docker pull
== docker image pull
and docker run
==
docker container run
.$ docker --version
Docker version 26.0.0, build 2ae903e
$ docker info
Client: Docker Engine - Community
Version: 26.0.0
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.13.1
Path: /usr/libexec/docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v2.25.0
Path: /usr/libexec/docker/cli-plugins/docker-compose
Server:
Containers: 51
Running: 49
Paused: 0
Stopped: 2
Images: 49
Server Version: 26.0.0
Storage Driver: overlay2
Backing Filesystem: xfs...
Commands shown:
docker run <image>
docker run -it <image> <command>
docker run -d <image> <command>
docker attach
# Ctrl+P,Q to detach
docker stop <container>
docker kill <container>
docker images
docker ps -a
docker pull <image>
docker pull <image:tag>
ID
(e.g. 15895ef0b3b2
) or name:tag
(e.g. fedora:latest
)docker push
to internal image registrydocker pull
/docker run
ping
)/var/lib/docker
(depending on storage driver)latest
aufs
, devicemapper
, …)Commands shown:
docker pull <image>
docker pull -a <image>
docker images
docker history <image>
docker rmi <image>
hello-world
image in a new containerdocker run --name <name>
docker stop
(sends SIGTERM
to PID 1), docker kill
(sends SIGKILL
to PID 1) and docker restart
which are realized using Kernel Namespaces
cgroup
s are used to limit resource usage (CPU, network, etc.)init
, so child processes are not terminated
gracefullyCommands shown:
docker run --name <name> <image> <command>
docker run -it <image> <command>
docker run -d <image> <command>
docker run --rm <image>
docker exec -it <container> <command>
docker top <container>
docker attach <container>
docker start <container>
docker stop <container>
docker restart <container>
docker ps
docker ps -a
docker rm <container>
docker create --name <container> <image>
docker tag <image> <tag>
docker inspect <container>
docker port <container>
docker logs [-f] <container>
docker commit <container> <image>[:<tag>]
docker save -o <file> <image>
tar -tf <file>
docker rmi <image>
docker load -i <file>
docker run -p <public port>:<internal port> <image>
docker run -v <local path>:<container path> <image>
ping
in the backgroundalpine
imagealpine
image with ping 8.8.8.8
commandping
ubuntu
container and step into itcurl
and exitcurl_example:1.0
curl https://www.google.com
Dockerfile
(casing matters!)FROM <base image>[:<tag>]
(which can be scratch
)LABEL maintainer "<name and email>"
)COPY
, ADD
RUN
EXPOSE
VOLUMES
USER
ENV
ENTRYPOINT
, CMD
# Comments
docker commit
after each line)COPY
and ADD
,
RUN
is cached based on line following RUN
BUILD_ARG
s.dockerignore
filesENTRYPOINT
statementdocker run <image> <args>
ENTRYPOINT
script./docker-entrypoint.sh teamcity-server run
is the defaultdocker run -it agross/teamcity bash
.dockerignore
fileRUN
statementsupervisord
and the like)Sort multi-line arguments and indent 4 spaces:
RUN apt-get update && apt-get install --yes \
cvs \
git \
mercurial \
subversion
FROM
: Use current official repositoriesRUN
: Split long or complex RUN
statements across multiple lines separated
RUN command-1 && \
command-2 && \
command-3
RUN apt-get upgrade
Use the JSON array format for CMD
to prevent an additional shell as PID 1
CMD ["executable", "param1", "param2", "..."]
CMD ["apache2", "-DFOREGROUND"]
CMD ["perl", "-de0"]
CMD ["python"]
CMD ["php", "-a"]
ENTRYPOINT
only when requiredEXPOSE
the usual ports for your applicationsCOPY
over ADD
Leverage the build cache, disable if necessary:
docker build --no-cache=true -t <image>[:<tag>] .
# Will use cache unless requirements.txt change.
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
# Will use cache unless any file changes.
COPY . /tmp/
Do not use ADD
to download files, although it’s possible. Use RUN
with
curl
, unzip/...
and rm
instead to keep images small:
# Bad - 3 layers.
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
# Good - 1 layer.
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all
gosu
when required to run as non-rootDockerfile
in a running container, REPL-styleHEALTHCHECK
in the Dockerfile or run containers with
--health-{cmd,interval,timeout,retries}
VOLUME
sCommands shown:
docker build .
docker build --tag <image>:<tag> .
docker build --build-arg <arg>=<value>
docker run --env <var>=<value>
docker run -p <external port>:<internal port> <image>
docker run -p <external port>:<internal port>/udp <image>
docker run -p <ip address>:<external port>:<internal port> <image>
docker run -P <image>
docker port <container>
Dockerfile
for https://github.com/agross/docker-hello-appTHE_ANSWER=42
THE_ANSWER
is thereCreate a new file index.jade
in the current directory:
html
body Content from the host
/app/views/
directory with the
directory that contains the index.jade
file above/app/views/index.jade
(e.g. using echo
)index.jade
on your hostdocker login [<server>]
(login info is persisted)
if you want to use docker push <image>
<hub user name>/<repo>[:<tag>]
to be able to pushdocker run -d -p 5000:5000 --name registry registry
docker login [<server>]
docker push <image>
hello
under your user account on Docker Hub<hub user name>/hello
https://hub.docker.com/r/<hub user name>/hello/tags/
for your uploaded
imagedocker volume ls
docker volume create <name>
docker run -d <image>
docker run -d -v <name>:<mount point> <image> <command>
pings
alpine
with sh -c 'ping 8.8.8.8 > /data/ping.txt'
with /data
being
mounted to the pings
volumealpine
container that mounts pings
and inspect pings.txt
contents using tail -f
--network
hello
agross/hello
on the hello
network, but --name
them
differently (one
and two
) and to not publish portsone
and two
, look for network settingshello
networkone
or two
and try to ping
the otherRun nginx on the hello
network
docker run --rm -d --name nginx --network hello -p 80:80 nginx
Write a nginx config file (hello.conf
) that uses one
and two
as
upstreams:
upstream hello {
server one:8080;
server two:8080;
}
server {
listen 80;
location / {
proxy_pass http://hello;
}
}
Restart nginx, this time with the conf above bind-mounted to
/etc/nginx/conf.d/default.conf
docker run -d --name nginx --network hello -p 80:80 -v $PWD/hello.conf:/etc/nginx/conf.d/default.conf nginx
wordpress
and mariadb
Run a MariaDB container on the network from step 2 and inject some environment variables:
MARIADB_ROOT_PASSWORD=secret
MARIADB_DATABASE=wordpress
MARIADB_USER=wordpress
MARIADB_PASSWORD=wordpress
Is there a better way than multiple --env
parameters? Hint: Use a file.
Run WordPress on the network from step 2 and tell it where it can find the database server:
WORDPRESS_DB_HOST=<mariadb container name>:3306
WORDPRESS_DB_USER=wordpress
WORDPRESS_DB_PASSWORD=wordpress
docker-compose
!docker compose
use-casesdocker compose
creates a per-composition network by default, named after
the current directory (unless docker compose -p <name> ...
is specified).docker compose up
runs it finds any containers from previous runs and
reuses the volumes from the old container (i.e. data is restored).docker compose
reuses
existing containers because it caches the configuration bits that were used to
create a container.Variables in the docker-compose.yaml
file can be used to customize the
composition for different environments.
web:
ports:
- "${EXTERNAL_PORT}:5000"
Override settings for different environments,
e.g. production.yml
containing changes specific for production:
docker compose -f docker-compose.yml -f production.yml up -d
docker-compose scale <service>=<instances>
docker compose
Since WordPress cannot run without a database connection we need to ensure that the database is ready to accept connections before WordPress starts. But Docker does not really care about startup order and service readiness. There are several solutions to this problem. Some involve using external tools like:
Using these external tools require you to change a container’s ENTRYPOINT
or
CMD
(depending on how the image defines those). This change involves defining
the dependency using the external tool and also telling the external tool what
it means to start e.g. WordPress.
Docker’s builtin method, which is only available to docker-compose.yaml
files
using version: 2
(e.g 2.x
), is to define a HEALTHCHECK
-based dependency.
Here the dependent container (database) must define healthiness and the
depending container (WordPress) can then define its dependency to be satisfied
if the dependent is healthy.
services:
db:
image: mysql
# MySQL does not come with a HEALTHCHECK, so we need to define our own.
healthcheck:
# This check tests weather MySQL is ready to accept connections.
test: ["CMD", "mysql", "--user", "root", "--password=secret", "--execute", "SELECT 1;"]
# Allow 15 seconds for MySQL initialization before running the first check.
start_period: 15s
app:
image: wordpress
# Start the WordPress container after the database is healthy.
depends_on:
db:
condition: service_healthy
You may use either method in the next exercise.
my-wordpress
and enter itdocker-compose.yaml
Paste the following:
services:
mariadb:
image: mariadb
environment:
MARIADB_ROOT_PASSWORD: secret
volumes:
- ./wp/db/conf:/etc/mysql/conf.d:ro
- ./wp/db/data:/var/lib/mysql
# Required because of "condition: service_healthy" below.
healthcheck:
test: ["CMD", "mariadb", "-uroot", "-psecret", "-e", "SELECT 1;"]
start_period: 5s
interval: 5s
wordpress:
image: wordpress:php7.0
# Define "db" alias for the mariadb service.
links:
- mariadb:db
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_PASSWORD: secret
ports:
- 80:80
restart: always
volumes:
- ./wp/data:/var/www/html/wp-content
# Define startup order.
depends_on:
mariadb:
condition: service_healthy
smtp:
condition: service_started
smtp:
image: mwader/postfix-relay
restart: always
environment:
POSTFIX_myhostname: example.com
docker compose up
docker-compose.yaml
such that the php7.1
tag is used for wordpress
Rebuild and restart the WordPress container:
docker compose up --no-deps -d wordpress
mariadb
to 2 instancesmariadb
using docker compose exec
. In which instance did you
step?mariadb
instances are there?down
the application and remove all containers, networks, etc.logs
for the composition and for a single servicekill $(pgrep -f apache)
. Is the container restarted? Why?docker compose stop
)Add a new top-level section:
volumes:
wp-db-conf:
wp-db-data:
wp-data:
volumes:
to use volume mounts instead of bind mounts.
E.g. - ./wp/mariadb/data:/var/lib/mysql
becomes - wp-db-data:/var/lib/mysql
agross/hello
needs a databasedocker-compose.yaml
file that builds a composition of
agross/hello
’s source
code at https://github.com/agross/docker-hello
and e.g. MariaDB?