docker

Posted by neverset on August 9, 2020

dockerfile tips

caching

For an efficient use of the caching mechanism , we need to place the instructions for layers that change frequently after the ones that incur less changes. application’s dependencies change less frequently than the Python code. Therefore we copy the dependencies file and install them and then we copy the source code

# copy the dependencies file to the working directory
COPY requirements.txt .
# install dependencies
RUN pip install -r requirements.txt
# copy the content of the local src directory to the working directory
COPY src/ .

BuildKit’s new caching

you can cache a directory across builds with Buildkit(normally each build is its own self-contained little filesystem)

# syntax = docker/dockerfile:1.2
FROM python:3.9-slim-buster
COPY requirements.txt .
#caching the /root/.cache directory, since that is also where Pipenv and Poetry will store their files; pip uses ~/.cache/pip by default
RUN --mount=type=cache,target=/root/.cache \
    pip install -r requirements.txt
# ... etc. ...

during docker build you need to activate buildkit with

export DOCKER_BUILDKIT=1

the limitation with buildkit is that BuildKit caching would not work in cloud docker building, since cloud CI service starts with a new environment every time

Multi-stage builds

using multi-stage builds can strip the final application image of all unnecessary files and software packages and deliver only the files needed to run our Python code.

# first stage
FROM python:3.8 AS builder
COPY requirements.txt .
# install dependencies to the local user directory (eg. /root/.local)
RUN pip install --user -r requirements.txt
# second unnamed stage
FROM python:3.8-slim
WORKDIR /code
# copy only the dependencies installation from the 1st stage image
COPY --from=builder /root/.local/bin /root/.local
COPY ./src .
# update PATH environment variable
ENV PATH=/root/.local:$PATH
CMD [ "python", "./server.py" ] 

Compose file tips

to start up all services docker-compose up -d to stop and remove all project containers docker-compose down to rebuild images docker-compose build to check logs docker-compose logs app

Network separation

to avoid all service in same default network, we need to define separate networks for unrelevant components

Docker Volumes

To persist DB data between different containers, we can exploit named volumes.

Docker Secrets

version: "3.7"
services:
db:
    image: mysql:8.0.19
    command: '--default-authentication-plugin=mysql_native_password'
    restart: always
    secrets:
    - db-password
    volumes:
    - db-data:/var/lib/mysql
    networks:
    - backend-network
    environment:
    - MYSQL_DATABASE=example
    - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password

app:
    build: app
    restart: always
    secrets:
    - db-password
    networks:
    - backend-network
    - frontend-network

web:
    build: web
    restart: always
    ports:
    - 80:80
    networks:
    - frontend-network
volumes:
db-data:
secrets:
db-password:
    file: db/password.txt
networks:
backend-network:
frontend-network:

Applying Code Updates & debugging

update code updates

during debugging, we can bind-mount the source code to container, so that we can debug code without rebuild image and redeploy container

app: build: app restart: always volumes: - ./app/src:/code

A reloader watches all the source code files and automatically restarts the server when detects that a file has changed. To enable the debug mode we only need to set the debug parameter as below:

#server.py
server.run(debug=True, host='0.0.0.0', port=5000)

debugging

  • map locally the port to the debugger app: build: app restart: always volumes: - ./app/src:/code ports: - 5678:5678
  • import dubugger modul vs code support ptvsd

    import ptvsd ptvsd.enable_attach(address=(‘0.0.0.0’, 5678))

  • create remote launch

    #in launch.json file { “version”: “0.2.0”, “configurations”: [ { “name”: “Python: Remote Attach”, “type”: “python”, “request”: “attach”, “port”: 5678, “host”: “localhost”, “pathMappings”: [ { “localRoot”: “${workspaceFolder}/app/src”, “remoteRoot”: “/code” } ] } ] }

docker volume

docker is based on Union File System (changes lives on docker layer, if container is deleted, changes are lost), docker volume can help persistent data storage, sharing data betwenn docker containers and seperation of data and container

init volume

if no host path is given, default path is /var/lib/docker. the mount path will be created there

#bind-mounts /tmp/data1 from the host on to docker /tmp/data2
docker run -itd --volume /tmp/data1:/tmp/data2 --name container-test ubuntu bash
#mount volume without specifying host path
docker run -v /some/dir
#mount volume using Dockerfile
VOLUME /tmp/data1

create volume

docker volume create portainer_data

inspect volume

#inspect setting of the volume
docker inspect --format='' container-test | python -m json.tool

data sharing

docker run --name my_container -v /some/path ...
docker run --volumes-from my_container --name my_container2 ubuntu /bin/bash

data-only container

data container does not need to be run to be accessable, so it does not take extra space #there is already volume definition in postgres image docker run –name dbdata postgres echo “Data-only container for postgres” #backup data in data container to tar docker run –rm –volumes-from dbdata -v $(pwd):/backup debian tar cvf /backup/backup.tar /var/lib/postgresql/data

permission

file changes after VOLUME command in Dockerfile do not change the Volume

#do change before VOLUME
FROM debian:wheezy
RUN useradd foo
RUN mkdir /data && touch /data/x
RUN chown -R foo:foo /data
VOLUME /data

delete volumes

using docker rm without -v will not delete volume. volume will be delete when no container is using it. bind-mounts volume refering to host folder will never be deleted #to delete volume docker rm -v #or using –rm when doing docker run docker run –rm

docker-compose

docker-compose is a tool to define and run multi-docker application. Containers are defined in service tag in yaml file

#execuate docker-compose
docker-compose up
#run docker-compose in background
docker-compose up -d
#not using default docker-compose.yml file
docker-compose -f server.yml up -d

volume

mount volume from host to container (HOST:CONTAINER) or(HOST:CONTAINER:ro) can rebuild images if change code is needed

volumes:
    data-volume:
      - ../src:/opt/src

build

image: webapp:tag
build:
  context: ./dir
  dockerfile: Dockerfile-alternate
  args:
    buildno: 1

command

default command to run when docker starts

command: bundle exec thin -p 3000

access service in other containers

web:
    links:
    - db
    - db:database

access service in containers outside docker-compose

external_links:
    - redis_1
    - project_db_1:mysql

expose

expose port to linked service

expose:
- "3000"

ports

expose container port to host

ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"

restart

restart policy if error occurs

restart: "no"
restart: always
restart: on-failure
restart: unless-stopped

environment

set environment variable, if no value (only key) is given, the value on host will be taken

environment:
    RACK_ENV: development
    SHOW: 'true'
    SESSION_SECRET:

pid

container and host can share pid space

pid: "host"

dns

config dns server

dns: 8.8.8.8
dns:
- 8.8.8.8
- 9.9.9.9 ## docker manager tool portainer.io ### installation

sudo docker volume create portainer_data
sudo docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

url

http://dockerIP:9000