# first thing first, try use the docker management commands over commands, for example `docker container run` over `docker run` (also the alias for docker container run)
docker container run alpine sh
# -i let you send commands to container
# -t show you the prompt
docker container run -it alpine sh
cat /etc/os-release
# display host machine information
uname -r
Start and stop, attach and detach
docker container run --name c1 -it alpine sh
# ctrl + d terminates the container
# ctrl + p + q detaches the container (this will only work if -it was applied)
docker container ls
# take a note of the alpine container name
docker container attach c1
# re-attach to that container
# list all container regardless they're running or not
docker container ls -a
# restart container
docker container start container_name
# stop container
docker container stop container_name
# or docker container kill container_name
# start container in detach mode
docker container run --name c2 -itd alpine sh
# or docker container run -d alpine
# -it flag starts a interactive processes (tty)
# start a burn-after-use container
docker container run --rm hello-world
# start a container with a custom name
docker container run --name my_container hello-world
Clean things up
docker container rm container_name
# remove all containers (only works in certain shell)
docker container rm $(docker container ls -aq)
# remove image
docker image rm image_name
Communication between containers
# start two containers with --link flag so that they could talk to each other by the name
docker container run -it --rm --name c1 alpine sh
docker container run -it --rm --name c2 --link c1 alpine sh
# link is depreciated though
Use user defined network for cross container communication
# list all available network
docker network ls
# among which the 'bridge' is the default one, container created without specifying network will attach to this one
# create network
docker network create test
# create container and attach to test network
docker container run -it --rm --name c1 --network test alpine sh
docker container run -it --rm --name c2 --network test alpine sh
# now can talk between containers using custom names, `ping c1` from c2 and versus versa
Image basics
Manage images
# list images
docker image ls
# if no tag was given when deleting a image, the default tag (latest) will be applied
docker image rm image_name:latest
# download image
docker image pull image_name:latest
Creating images
# Dockerfile
# create a image on top of alpine with bash
FROM alpine:latest
RUN apk update
RUN apk add bash
# CMD instruction is the shell format wraps the command inside a sh -c
# it exec format calls the executable directly
CMD bash
# . stands for the context which is the current directory in this case
docker image build -t myalpine:latest .
docker container run -it myalpine:latest
Pushing images
# login to registry first
docker login
# namespace your image
docker image tag myalpine:latest slashbit/myalpine:latest
docker image push slashbit/myalpine:latest
Container basics
Visit your container with exec
# start container with in-interactive and detach mode
docker container run --name c1 -d nginx
# execute command inside of the container
docker container exec c1 cat /etc/nginx/nginx.conf
# starting shell
docker container exec -it c1 sh
Debugging
# say this is your Dockerfile
FROM debian:buster-slim
RUN apt-get update
RUN apt-get install -y nginx
# start nginx in the foreground
CMD nginx -g 'daemon off;'
# start container
docker image build -t slashbit/nginx . && docker container run --name c1 --rm slashbit/nginx
# then you find ctrl + c does not stop the container
# start debugging by entering the container from another shell
docker container exec -it c1 bash
# install ps in order to take a closer look at the running processes
apt-get update && apt-get install -y procps
ps -ef
# you'll find pid 1 is taken by the shell and it does not forward the term signal to the process running nginx
# fixing dockerfile
# instead of CMD nginx -g 'daemon off;'
# use exec form instead of shell form to start nginx to ensure nginx uses process 1 to start, which will be receiving the terminate signal
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
Reading logs
# start nginx on port 80
docker container run --rm --name c1 -p 80:80 --rm nginx:latest
# logs will be piped to the console
# however, if this is how you start the container
docker container run --rm --name c1 -p 80:80 --rm -d nginx:latest
# to access the logs, you'll need to
docker container logs c1
# to follow the logs with -f
docker container logs -f c1
Using logfiles
docker container run --rm --name c1 -p 80:80 --rm nginx:latest
# once container started, the only way accessing the log is
docker container exec c1 tail /var/log/nginx/access.log
# another alternative would be link the log file to stdout by updating Dockerfile
FROM debian:buster-slim
RUN apt-get update
RUN apt-get install -y nginx
RUN rm /var/log/nginx/access.log && ln -s /dev/stdout /var/log/nginx/access.log
# link error log to stderr as well
RUN rm /var/log/nginx/error.log && ln -s /dev/stderr /var/log/nginx/error.log
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
Persisting data
Mounting data
Bind mounts: A bind mount is a file or folder stored anywhere on the container host filesystem, mounted into a running container.
Volumes: Volumes are the preferred way to store persistent data Docker containers create or use.
# bind mount the volume as read only
docker container run --volume /Users/slashbit/html:/var/www/html:ro -p 80:80 slashbit/nginx
# shortening the command
docker container run -v $(pwd)/html:/var/www/html:ro -p 80:80 slashbit/nginx
# alternative, use --mount option
docker container run --mount type=bind,src="$(pwd)/html",destination=/var/www/html,readonly -p 80:80 slashbit/nginx
Mounting volume
# when container gets deleted, volume remains
# mounting anonymous volume
# create (only if it does not exist) a container managed volume inside container
docker container run -it --volume /data alpine
# verify it's created
df -h
# list the volumes
docker volume ls
# mounting named volume
docker container run -it --volume my-volume:/data alpine
# use mount option instead
# mounting anonymous volume
docker container run -it --mount dst=/data alpine
Deleting volume
# create volume
docker volume create volume_name
# inspect volume
docker volume inspect volume_name
# delete single volume
docker volume rm volume_name
# delete all unused volume
docker volume prune
# docker will automatically delete anonymous volumes if --rm was used to start the container
docker container run --rm -v /data alpine echo hello
Using volumes from other containers
docker container run -it --rm --name c1 -v test-data:/data alpine sh
docker container run --volume-from c1 -it alpine sh
When to use bind mount vs. volume
use bind mount when you want to access data that resides on your docker host from a container
source code
configuration files
use volume when you want to persist data from the container, and share data between containers
database
backups
configuration
Data in images
Copying data into image
docker container run --volume /Users/slashbit/html:/var/www/html:ro -p 80:80 slashbit/nginx
docker container run -v $(pwd)/html:/var/www/html:ro -p 80:80 slashbit/nginx
# copy from host into specify path inside image
COPY ./html /var/www/html
# using wild-card for copying (don't forget the / at the end)
COPY ./html/*.html /var/www/html/
# copy multiple files
# note that files inside of assets and css will be copied over but not the folder itselt, which means all files inside of assets and css will all end up into the html folder
COPY ./html/*.html ./html/assets/ ./html/css/ /var/www/html/
docker container run --mount type=bind,src="$(pwd)/html",destination=/var/www/html,readonly -p 80:80 slashbit/nginx
# build with current directory as context
docker image build -t slashbit/nginx:latest .
The magic of ADD
# use the same example as the last one
# replacing COPY with ADD
ADD ./html/ /var/www/html/
# ADD can extract the archive tar file automatically
ADD ./html.tar.gz /var/www/
# ADD can also download the source from a remote url
ADD http://example.com/index.html /var/www/html/example.html
# use ADD over COPY only when you need these two magic features
Ignoring files
# ./dockerignore
# not ADDing data inside of this directory
data/tmp/
Container behavior
Using environment variables to change container behavior at runtime
# set host name and current path to bash prompt
# using -e to pass env variables
docker container run -it -e "PS1=\h:\w# " slashbit/alpine
ENV instruction in Dockerfile
FROM alpine:latest
RUN apk update
RUN apk add bash
# set up multiple env variables
# -e flat passed when start container will overwrite ENV
ENV PS1="\h:\w# " PS2=">> "
CMD bash
# output container env vars
docker container run alpine env
# assign using .env file
docker container run --env-file app.env alpine env
# using env vars from the host
docker container run -e HOST_ENV_VAR alpine env
Config postgres
# say you have postgres install on the host machine
psql -h lcoalhost -U slashbit test
# start the postgres container in the background (-d)
docker container run --rm --name pg -d -e "POSTGRES_PASSWORD=secret" postgres:9.6.6-alpine
# start another container to connect to pg container
# use --link option so that pg (name) can be resolved
docker container run --rm --link pg -it postgres:9.6.6-alpine psql -h pg -U myuser
# or if there's a webapp needs to be connected to database and ran in another container
docker container run --link pg --env-file app.env -p 9292:9292 slashbit/demo_web_app
Managing containers with docker compose
# docker-compose.yml
version: '3.3'
services:
web:
image: nginx:latest
ports:
- 80:80
# bind mounts volumes to container
volumes:
- ./html:/usr/share/nginx/html
pg:
image: postgres:9.6-alpine
# -e
# environment:
# - POSTGRES_DB=test
# alternative using env_file
env_file:
- ./db.env
volumes:
# persist data using a named volume
- pg-data:/var/lib/postgresql/data
webapp:
# use wait_for to ensure pg service starts first
image: slashbit/demo_web_app:wait_for_pg
# manage dependencies
depends_on:
pg
ports:
- 9292:9292
environment:
# automatically pick up the env vars inside of .env file
- POSTGRES_DB
- POSTGRES_USER
# env_file:
# - ./app.env
alpine:
image: alpine:latest
# same as -it
stdin_open: true
tty: true
command: sh
# ask docker to create the resources needed
volumes:
pg-data: # the named volume
docker-compose up
# or start a specify service
# and since this service has the dependency of pg, docker compose will start the pg service automatically
# docker-compose up -d webapp
# delete resources (containers)
docker-compose down
# check container processes
docker-compose ps
# start shell
docker-compose exec alpine sh # to talk to another container just use its name: ping pg
# stop service
docker-compose stop alpine
# remove service
docker-compose rm alpine
# start again (alpine will be re-created)
docker-compose up -d
Getting started
Start and stop, attach and detach
Clean things up
Communication between containers
Use user defined network for cross container communication
Image basics
Manage images
Creating images
Pushing images
Container basics
Visit your container with exec
Debugging
Reading logs
Using logfiles
Persisting data
Mounting data
Mounting volume
Deleting volume
Using volumes from other containers
When to use bind mount vs. volume
Data in images
Copying data into image
The magic of ADD
Ignoring files
Container behavior
Using environment variables to change container behavior at runtime
ENV instruction in Dockerfile
More on env vars
Config postgres
Managing containers with docker compose