genkio / blog

Stay hungry stay foolish
https://slashbit.github.io/blog/
0 stars 1 forks source link

Docker 201 #183

Open genkio opened 5 years ago

genkio commented 5 years ago

before start

# hide legacy commands
export DOCKER_HIDE_LEGACY_COMMANDS=true

Getting started

# 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 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

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
docker image build -t slashbit/alpine && docker container run -it slashbit/alpine

More on env vars

# 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