# Docker

Docker 是一個開放原始碼軟體，是一個開放平台，用於開發應用、交付應用、執行應用。 Docker允許用戶將基礎設施中的應用單獨分割出來，形成更小的顆粒，從而提高交付軟體地速度。 Docker容器 與虛擬機器類似，但原理上，容器是將作業系統層虛擬化，虛擬機器則是虛擬化硬體，因此容器更具有可攜式性、高效地利用伺服器。

# Secure Docker Network

##### With SWAG

[SWAG - Secure Web Application Gateway](http://www.osslab.tw/books/docker/page/swag---secure-web-application-gateway "SWAG - Secure Web Application Gateway")

##### With iptables

- [從 Linux 基礎實現 Docker Bridge 網路：一步步理解容器通訊 (6) - iT 邦幫忙](https://ithelp.ithome.com.tw/articles/10369528)

對於 Container 不同啟動方式，在 iptables 的腳本設計會有些不同。

- docker-compose: 透過橋接的網路介面(br-\*) 與 `DOCKER-USER` 作限制。
- docker: 透過 `PRE_DOCKER` 作限制。

for docker-compose)

```shell
#!/usr/bin/env bash

# Purpose: Restrict the access to the container from external IPs.
# Author: Alang, 2019/8/14
#

CONTAINER="nginx_mysql_web_1"
EXT_IF="eth0"
BRG_IF="$(ip a | awk '/br-[a-z0-9]+:/{print $0}' | awk '{print $2}' | sed 's/://g')"

if [ ! -x /usr/bin/docker ]; then
    echo "Abort: Unable to run the docker command!"
    echo "Recommendation: Check if the Docker was installed."
    exit 1
fi

if [ -z "$BRG_IF" ]; then
    echo "Abort: The network interface (br-????) with container not found. "
    exit 1
fi

# Check the network interfaces
for i in $EXT_IF $BRG_IF;do
    if ! (ip a show $i >/dev/null 2>&1); then
        echo "Abort: The network interface $i doesn't exist"
        exit 1
    fi
done

# Check if the DOCKER-USER chain exists, if it does flushing the rules.
iptables -C FORWARD -j DOCKER-USER 2> /dev/null

if [ $? -eq 0 ]; then
    # Flush all existing rules
    iptables -F DOCKER-USER
else
    echo "Abort: The chain DOCKER-USER not found!"
    echo "Recommendation: "
    echo "  - Is the Docker daemon running?"
    echo "  - Is the version of Docker 17.06+?"
    exit 1
fi

## Docker container named www-nginx public access policy
## If using docker-compose, change FORMAT to
## --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
##
#WWW_IP="$(/usr/bin/docker inspect --format {{.NetworkSettings.IPAddress}} $CONTAINER 2>/dev/null)"
WWW_IP="$(/usr/bin/docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $CONTAINER 2>/dev/null)"

if [ -z "$WWW_IP" ];then
    echo "Abort: The docker IP isn't detected!"
    echo "Recommendation:"
    echo "  - Is the specified container UP?"
    echo "  - Adjsut the variable \$WWW_IP."
    exit 1
fi

# Default action
iptables -I DOCKER-USER -i $EXT_IF -j DROP   

## Insert web server container filter rules  
## Allow All IPs to access 80 & 443
#iptables -I DOCKER-USER -i $EXT_IF -p tcp -d $WWW_IP --dport 80  -j ACCEPT
#iptables -I DOCKER-USER -i $EXT_IF -p tcp -d $WWW_IP --dport 443 -j ACCEPT
## Allow Cloudflare IPs to acces 443
## Check the IP list of Cloudflare at the URL https://www.cloudflare.com/ips-v4
iptables -I DOCKER-USER -o $BRG_IF -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I DOCKER-USER -i $BRG_IF ! -o $BRG_IF -j ACCEPT
iptables -I DOCKER-USER -m state --state RELATED -j ACCEPT
iptables -I DOCKER-USER -i $BRG_IF -o $BRG_IF -j ACCEPT

iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 173.245.48.0/20  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 103.21.244.0/22  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 103.22.200.0/22  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 103.31.4.0/22    --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 141.101.64.0/18  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 108.162.192.0/18 --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 190.93.240.0/20  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 188.114.96.0/20  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 197.234.240.0/22 --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 198.41.128.0/17  --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 162.158.0.0/15   --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 104.16.0.0/12    --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 172.64.0.0/13    --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 131.0.72.0/22    --dport 443 -j ACCEPT

# My IPs
#iptables -I DOCKER-USER -i $EXT_IF -p tcp  -d $WWW_IP -s 219.70.88.133    --dport 443 -j ACCEPT


[ $? -eq 0 ] && echo "Done."
## EOL
```

for docker)

```shell
#!/usr/bin/env bash

# Usage:
# timeout 10 docker_iptables.sh
#
# Use the builtin shell timeout utility to prevent infinite loop (see below)

CONTAINER="raida17"

if [ ! -x /usr/bin/docker ]; then
    exit
fi

# Check if the PRE_DOCKER chain exists, if it does there's an existing reference to it.
iptables -C FORWARD -o docker0 -j PRE_DOCKER 2> /dev/null

if [ $? -eq 0 ]; then
    # Remove reference (will be re-added again later in this script)
    iptables -D FORWARD -o docker0 -j PRE_DOCKER
    # Flush all existing rules
    iptables -F PRE_DOCKER
else
    # Create the PRE_DOCKER chain
    iptables -N PRE_DOCKER
fi

# Default action
iptables -I PRE_DOCKER -j DROP

# Docker Containers Public Admin access (insert your IPs here)
#iptables -I PRE_DOCKER -i eth0 -s 192.184.41.144 -j ACCEPT
#iptables -I PRE_DOCKER -i eth0 -s 120.29.76.14 -j ACCEPT

# Docker Containers Restricted LAN Access (insert your LAN IP range or multiple IPs here)
#iptables -I PRE_DOCKER -i eth1 -s 192.168.1.101 -j ACCEPT
#iptables -I PRE_DOCKER -i eth1 -s 192.168.1.102 -j ACCEPT

# Docker internal use
iptables -I PRE_DOCKER -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I PRE_DOCKER -i docker0 ! -o docker0 -j ACCEPT
iptables -I PRE_DOCKER -m state --state RELATED -j ACCEPT
iptables -I PRE_DOCKER -i docker0 -o docker0 -j ACCEPT

# Docker container named www-nginx public access policy
# If using docker-compose, change format to
# --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
WWW_IP_CMD="/usr/bin/docker inspect --format {{.NetworkSettings.IPAddress}} $CONTAINER"
WWW_IP=$($WWW_IP_CMD)

# Double check, wait for docker socket (upstart docker.conf already does this)
while [ ! -e "/var/run/docker.sock" ]; do echo "Waiting for /var/run/docker.sock..."; sleep 1; done

# Wait for docker web server container IP
while [ -z "$WWW_IP" ]; do echo "Waiting for $CONTAINER IP..."; WWW_IP=$($WWW_IP_CMD); done

## Insert web server container filter rules
## Allow All IPs to access 80 & 443
#iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP --dport 80  -j ACCEPT
#iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP --dport 443 -j ACCEPT
## Allow Cloudflare IPs to acces 443
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 103.21.244.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 103.22.200.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 103.31.4.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 104.16.0.0/12 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 108.162.192.0/18 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 131.0.72.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 141.101.64.0/18 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 162.158.0.0/15 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 172.64.0.0/13 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 173.245.48.0/20 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 188.114.96.0/20 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 190.93.240.0/20 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 197.234.240.0/22 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 198.41.128.0/17 --dport 443 -j ACCEPT
iptables -I PRE_DOCKER -i eth0 -p tcp -d $WWW_IP -s 199.27.128.0/21 --dport 443 -j ACCEPT


# Finally insert the PRE_DOCKER table before the DOCKER table in the FORWARD chain.
iptables -I FORWARD -o docker0 -j PRE_DOCKER
```

##### With iptables + ipset

- [Limit Docker Container Access to Certain IP Addresses](https://www.putorius.net/limit-docker-container-access-to-certain-ip-addresses.html)

##### With ufw

- [ufw + Docker](https://github.com/chaifeng/ufw-docker)

##### With Firewalld  


```shell
# Removing DOCKER-USER CHAIN (it won't exist at first)
firewall-cmd --permanent --direct --remove-chain ipv4 filter DOCKER-USER

# Flush rules from DOCKER-USER chain (again, these won't exist at first; firewalld seems to remember these even if the chain is gone)
firewall-cmd --permanent --direct --remove-rules ipv4 filter DOCKER-USER

# Add the DOCKER-USER chain to firewalld
firewall-cmd --permanent --direct --add-chain ipv4 filter DOCKER-USER

# Add rules (see comments for details)
firewall-cmd --permanent --direct --add-rule ipv4 filter DOCKER-USER 0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment "This allows docker containers to connect to the outside world"
## Change the Docker Subnet address to your network settings (Could be 172.18.0.0/16)
firewall-cmd --permanent --direct --add-rule ipv4 filter DOCKER-USER 0 -j RETURN -s 172.17.0.0/16 -m comment --comment "allow internal docker communication"
## Add an ip to allow access to the docker exposed port
firewall-cmd --permanent --direct --add-rule ipv4 filter DOCKER-USER 0 -p tcp -m multiport --dports 9392 -s 10.18.109.20/32 -j ACCEPT -m comment --comment "my allowed ip address to openvas port"

# Add as many ip or other rules and then run this command to block all other traffic
firewall-cmd --permanent --direct --add-rule ipv4 filter DOCKER-USER 0 -j REJECT -m comment --comment "reject all other traffic"

# restart the services 
systemctl stop docker
systemctl stop firewalld
systemctl start firewalld
systemctl start docker
```

# MySQL Backup

##### 從 Host 執行

backup-db.sh

```shell
#!/bin/bash

BKDIR="db_backups"
BKFILE="cloudcoin_raida#17.`date +%y%m%d`.sql"
DBUSER="ThisDBUser"
DBNAME="cloudcoin_raida"
CONTNAME="raida17"
CONTDIR="/opt/data/$BKDIR"
HOSTDIR="/docker_vol/raida/data/$BKDIR"
KEEP=2


[ -d $HOSTDIR ] || mkdir $HOSTDIR
cd $HOSTDIR
start_s=$(date +%s)
echo "*********** START `date "+%F@%T"` **************"

# Full Backup
echo "-> Back up Full DB "
docker exec $CONTNAME bash -c "mysqldump --single-transaction --add-drop-table -u $DBUSER  $DBNAME > $CONTDIR/$BKFILE"
retval=$?

if [ $retval -eq 0 ];then
    echo "... [success]"
else
    echo "... [failure]**"
    exit 1
fi

# Compress the files
echo "-> Compress the dump file "
gzip -f $HOSTDIR/$BKFILE

retval=$?
if [ $retval -eq 0 ];then
    echo "... [success]"
    BKFILE="$BKFILE.gz"
else
    echo "... [*failure*]"
    exit 1
fi

# Purge the old files
echo "-> Purge the old files"
ls_files=($(ls -lt cloudcoin_raida* | awk -F ' ' '{print $9}'))
len=${#ls_files[@]}
i=$KEEP
while (($i < $len));do
        rm -vf "${ls_files[$i]}"
        let i++
done

end_s=$(date +%s)
elapsed=$(( end_s - start_s  ))
echo 
echo "Elapsed: $elapsed seconds"
echo "Output File: $HOSTDIR/$BKFILE"
echo "*********** END `date "+%F@%T"` **************"
echo
```

# 基本指令操作

#### 常用指令

```bash
# List running containers
docker ps

# ssh into the container
docker exec -it <container-name> /bin/sh

# Restart a container
docker restart <container-name>

#Show running container stats
docker stats

# Check docker daemon disk space usage
docker system df

# Purge those unused images, networks, containers and volumes 
docker system prune

# Check the container log
docker logs <container-name>

# Seacrh docker registry for image
docker search <image-name>

# Create and start a container
docker run -it <image-name> /bin/bash

# Check container's exposed ports
docker port {container-name}
```

#### 線上求助

- [Docker Cheat-Sheet](https://github.com/xcad2k/cheat-sheets/tree/main/infrastructure/docker)

```
man docker <command>
man docker build
man docker rmi 
```

#### 管理 Images

```bash
## 搜尋 Docker Hub 上的 image name
docker search lamp

## 顯示已下載所有 image name
docker images

## 檢視既有 image 的詳細資訊
docker inspect <image-name>

## 網路下載 image
docker pull ubuntu:13.10

## 刪除已下載的 image
docker rmi <image-name>

## 刪除所有 images
docker rmi $(docker images -q)

## 刪除所有 images，除了 my-images 以外
docker rmi $(docker images | grep -v 'ubuntu\|my-image' | awk {'print $3'})

## 刪除所有 <none> 的有問題 images
docker rmi $(docker images -f "dangling=true" -q)

## 刪除與 myapp/myimage 相關的 <none> 的 images
docker rmi $(docker images myapp/myimage -f "dangling=true" -q)

## 列出所有 images 之間繼承的關係
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz images -t
```

#### 管理 Containers

```bash
## 開啟並進入 container 的 console
docker run -it <image-name> /bin/bash
docker run -it --name <container-name> <image-name> /bin/bash

## 以 daemon 方式啟動 container
docker run -d -p 11180:80 <image-name>
docker run -d --name web <image-name>
TIP: 啟動 container 時可以自訂名稱以方便管理

docker run -d -p 80:80 --rm <image-name>
加上 --rm 時，當停止 container 時，會自動被刪除(與 docker rm 指令相同)，且無法使用啟動指令
(docker start) 只能使用 docker run 啟動。 

## 檢查目前已經啟動的 containers
docker ps
docker ps -a

## 檢視開啟中 container 的詳細資訊，包含 Volumes、IP、Hostname 等等
docker inspect <container-id>

## 刪除指定的 container
docker rm <container-id> 

## 刪除所有的 containers
NOTE: 小心，這也會刪除正在執行的 container
docker ps -a -q | xargs -n 1 docker rm
docker rm $(docker ps -aq) 

## 刪除所有已經終止的 container
docker ps -a | grep "Exited" | awk '{print $1}' | xargs docker rm
docker rm $(docker ps --all -q -f status=exited)
NOTE: 這常用於在重新啟動 container 或 rebuild image 時遇到錯誤訊息的解決方法。

## 停止 container
docker stop <container-id>

## Stop all containers
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)
docker ps -aq | xargs docker stop

## 匯出 container
docker export <container-id>  > ubuntu-mysql.tar 

## 匯入 container
cat ubuntu-mysql.tar | docker import - <image-name>

## 跳離目前開啟中的 container
按下 Ctrl P 後再按 Ctrl Q
NOTE: 如果無法成功跳離，原因可能是 Ctrl+P 是 Bash 內定的快捷鍵（回到前一個指令）

## 重新進入開啟中的 container
docker attach <container-id>
或
docker attach <container-name>
如果 container 是以 daemon 啟動，改用以下方式
docker exec -it <container-id/name> /bin/bash

## 儲存開啟中 container 內容
docker commit <container-id> <image-name> 

## 顯示指定 container 的 IP
docker inspect <container-id> | grep IPAddress | cut -d '"' -f 4 
```

Check Container CPU and RAM Usage

```bash
docker stats
docker stats --no-stream
docker stats --no-stream -a
docker stats <container-name>
docker stats --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemPerc}}"
docker ps --no-trunc --format "{{.Names}}\t{{.ID}}"
```

#### 管理 Volumes

Docker 的 Data Volume 是一個很特別的目錄設計，主要用在不同 containers 之間的資料分享，永久保存資料等。

主要特點：

- 當 container 建立時，volume 目錄就會被產生。如果 base image 已經包含了 volume 的目錄名，該目錄內的原有的資料會被完整複製。
- volume 目錄可以分享以及重複被使用。
- 當 image 被更新時(commit)，volume 目錄內的資料不會被更新。
- 即使 container 被移除，volume 目錄的資料也會被保留。

```
// 啟用 volume
docker run -t -i -p 80:80 -v ${PWD}/webapp:/webapp alang/centos5-lamp_php51
```

> TIP:  
> 格式：-v &lt;host-dir&gt;:&lt;container-dir&gt;
> 
> 在 container 內會自動新增一個目錄名為 /webapp，儲存到這個目錄的所有資料都會被保留。
> 
> 被保留的資料會儲存到 host 的某個特定目錄內，即使 container 被移除，這些資料還是會存在，要如何找到這個特定目錄:
> 
> docker inspect -f {{.Volumes}} &lt;container-id&gt;
> 
> 一般預設會是  
> /var/lib/docker/vfs/dir/bfebd8cb6......

檢查 Volume 路徑

```bash
docker inspect -f {{.Mounts}} <container-id>

docker inspect <container-id> | jq ".[].Mounts"
```

Volume 管理

- 手動新增 Volume 時的路徑為：`/var/lib/docker/volumes/<volume-name>`
- 新增指令：`docker volume create <volume-name>`
- 掛載方式：`-v <volume-name>:<destination>:<options>` , options 有 ro 與 rw
- 直接以新名字掛載，系統會自動建立 volume，路徑為 `/var/lib/docker/volumes/target/_data/<volume-name>`

```bash
# 手動新增 volume
# Usgae: docker volume create <volume-name>

docker volume create mydata

# 列出目前 Volumes
docker volume ls

# Container 掛載 Volume
# Usage: -v <name>:<destination>
docker run -it -v mydata:/opt/mydata

# 刪除 Volume
docker volume rm mydata
```

#### Docker Network

```
# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                      NAMES
7ccaf6119fa8        nginx:latest        "nginx -g 'daemon of…"   2 days ago          Up 39 hours         0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   nginx_mysql_web_1
81a920bb51a6        nginx_mysql_php     "docker-php-entrypoi…"   2 days ago          Up 2 days           9000/tcp                                   nginx_mysql_php_1
437a7501198f        mariadb:10.3        "docker-entrypoint.s…"   2 days ago          Up 2 days           3306/tcp                                   nginx_mysql_db_1

# docker network ls
NETWORK ID          NAME                  DRIVER              SCOPE
852eff02220e        bridge                bridge              local
334d2b8571a4        host                  host                local
b97cae66a977        nginx_mysql_default   bridge              local
40d15afb34b4        none                  null                local

# docker network inspect -f '{{json .IPAM.Config}}' bridge | jq -r .[].Subnet
# docker network inspect -f '{{json .IPAM.Config}}' bridge | jq -r .[].Gateway

# brctl show
bridge name     bridge id               STP enabled     interfaces
br-b97cae66a977         8000.0242569e79ff       no              veth3ce8cbd
                                                        veth5129652
                                                        veth55dcdf7
docker0         8000.0242faff70bb       no

```

##### - 取得 container IP

```
## Method #1: By inspecting the container
docker inspect <container_id> | grep -i ipaddr
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container_id>

# get an IP address associated with a specific network
# docker container inspect -f '{{ .NetworkSettings.Networks.<NETWORK NAME>.IPAddress }}' <CONTAINER_ID_OR_NAME>
docker container inspect -f '{{ .NetworkSettings.Networks.bridge.IPAddress }}' ubuntu-ip

## Method #2: Using the container's shell
docker exec -it <container-name> sh
> ip
or
> ifconfig

# if you get the errors with 'command not found', following the below steps to install the relevant packages.
> apt update -qq
> apt install iproute2 -yqq

## Method #3: By inspecting the network itself
# docker network inspect <NETWORK NAME>
docker network inspect bridge | jq .[].Containers
docker network inspect bridge | jq '.[].Containers."<CONTAINER ID>".IPv4Address'

docker network inspect -f '{{json .Containers}}' bridge | \
jq '..|if type == "object" and has("Name") then select(.Name=="<CONTAINER NAME>") | .IPv4Address else empty end' -r
```

#### 管理 Docker

檢查版本資訊

```shell
# 檢查 Docker 版本
docker version

# Docker 更多資訊
docker info
```

host 與 container 間交換檔案

```shell
docker cp <container-name>:/etc/nginx/nginx.conf /data/web/conf
docker cp host_source_path my_container:destination_path
docker cp -a host_source_path my_container:destination_path
```

定期清除沒用的物件

```shell
# 這會清除所有已停止的 container，沒有在用的docker層網路介面與 <none> 不完整的 image。
docker images --quiet --filter "dangling=true"
docker system prune

# 上述指令會保留 volume 裡的資料，如果要一併清除，須加上 --volumes
docker system prune -a --volumes

# For volumes only
docker volume ls -f dangling=true
docker volume prune
```

清理指令 `docker system prune` 包含了以下程序

- It deletes all stopped containers
- It removes all unused networks
- It deletes all dangling images (and with -a, all unused images)
- Then it cleans up build cache and volumes (if --volumes is included)

#### Restart Policy

- [Beginner's Guide to Docker Restart Policy](https://linuxhandbook.com/docker-restart-policy/)

自動啟動 container

```shell
# Add --restart=unless-stopped
docker run -d -p 4449:4449 --name myst --restart=unless-stopped 
```

#### Docker Logging

- [Complete Beginner's Guide to Docker Logging](https://linuxhandbook.com/docker-logging/)

```shell
docker logs {container-name}
docker logs --tail 50 {container-name}
docker logs -f {container-name}
docker logs -f --tail 20 {container-name}

# View timestamp in Docker logs
docker logs -t {container-name}
docker -n=10 -t {container-name}

# Viewing Docker logs in a specified time period
docker logs --since 1440m -t {container-name}
docker logs --until 1440m -t {container-name}
docker logs --since 2021-07-28 -t {container-name} 
```

Docker system service logs

```shell
sudo journalctl -u docker
```

Where are Docker logs stored

```shell
sudo ls -lh /var/lib/docker/containers
```

#### Log Rotation for Container

##### - 全域設定

Edit `/etc/docker/daemon.json`

```JSON
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3" 
  }
}
```

Restart Docker daemon

```shell
sudo systemctl restart docker
```

##### - 個別設定

```bash
# Disable logging
docker run --log-driver=none

# Limit logging
docker run --log-driver=json-file --log-opt max-size=10m --log-opt max-file=3

# Verify the configuration
docker inspect {CONTAINER-NAME} | jq .[].HostConfig.LogConfig
```

#### Disk Space Usage

```
avimanyu@iborg-desktop:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          4         4         1.065GB   0B (0%)
Containers      4         4         5.705kB   0B (0%)
Local Volumes   7         7         1.108GB   0B (0%)
Build Cache     0         0         0B        0B
```

```
avimanyu@iborg-desktop:~$ docker system df -v
Images space usage:

REPOSITORY                               TAG       IMAGE ID       CREATED         SIZE      SHARED SIZE   UNIQUE SIZE   CONTAINERS
ghost                                    4.32.0    b40265427368   8 weeks ago     468.8MB   0B            468.8MB       1
jrcs/letsencrypt-nginx-proxy-companion   latest    037cc4751b5a   13 months ago   24.35MB   0B            24.35MB       1
jwilder/nginx-proxy                      latest    509ff2fb81dd   15 months ago   165MB     0B            165MB         1
mariadb                                  10.5.3    f5d2bcaf057b   20 months ago   407MB     0B            407MB         1

Containers space usage:

CONTAINER ID   IMAGE                                    COMMAND                  LOCAL VOLUMES   SIZE      CREATED        STATUS        NAMES
899cc90e85d9   ghost:4.32.0                             "docker-entrypoint.s…"   1               0B        8 weeks ago    Up 8 weeks    ghost_ghost_6
17b58fdafbce   jrcs/letsencrypt-nginx-proxy-companion   "/bin/bash /app/entr…"   4               571B      3 months ago   Up 2 months   letsencrypt-proxy-companion
58f99f46ee03   jwilder/nginx-proxy                      "/app/docker-entrypo…"   5               5.13kB    3 months ago   Up 2 months   jwilder-nginx-proxy
fb907286b60e   mariadb:10.5.3                           "docker-entrypoint.s…"   1               2B        3 months ago   Up 2 months   ghost_db_1

Local Volumes space usage:

VOLUME NAME                      LINKS     SIZE
ghostdb                          1         434.7MB
jwilder-nginx-with-ssl_acme      2         36.09kB
jwilder-nginx-with-ssl_certs     2         25.12kB
jwilder-nginx-with-ssl_dhparam   1         1.525kB
jwilder-nginx-with-ssl_html      2         1.106kB
jwilder-nginx-with-ssl_vhost     2         556B
ghost                            1         674MB

Build cache usage: 0B

CACHE ID   CACHE TYPE   SIZE      CREATED   LAST USED   USAGE     SHARED
```

```
avimanyu@iborg-desktop:~$ docker image ls
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
busybox       latest    beae173ccac6   6 weeks ago     1.24MB
ubuntu        latest    fb52e22af1b0   5 months ago    72.8MB
alpine        latest    49f356fa4513   10 months ago   5.61MB
hello-world   latest    d1165f221234   11 months ago   13.3kB
```

```
avimanyu@iborg-desktop:~$ docker ps --size
CONTAINER ID   IMAGE     COMMAND      CREATED         STATUS         PORTS     NAMES           SIZE
1171dcfb7e06   alpine    "sleep 10"   10 months ago   Up 9 seconds   
```

```
# Overlay2 is the default Docker storage driver on Ubuntu. 
# You can confirm this by running the 'docker info' command and looking for the Storage Drive
# To get the <<hash-named-directory> by the command 'docker inspect <image-name>'
sudo du -sh /var/lib/docker/overlay2/<hash-named-directory>/
```

```
# Specific Volume Disk Usage
$ docker volume ls
DRIVER    VOLUME NAME
local     d502589845f7ae7775474bc01d8295d9492a6c26db2ee2c941c27f3cac4449d1
local     e71ee3960cfef0a133d323d146a1382f3e25856480a727c037b5c81b5022cb1b
local     test-data

$ sudo du -sh /var/lib/docker/volumes/test-data/_data
4.0K	/var/lib/docker/volumes/test-data/_data
```

#### Limit CPU &amp; Memory

```bash
docker run --memory=512m --cpus=1
```

Docker Compose

```yaml
services:
  app:
    image: myimage
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 256M
```

顯示 container 資源使用狀況

```bash
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.PIDs}}"
```

#### FAQ

##### - 無法移除 image

> rror response from daemon: conflict: unable to delete dd78a816fb76 (must be forced) - image is referenced in multiple repositories

Solution: 如果同一個 image id 有兩個不同 image 名稱，在刪除這個 image id 時可能會遇到類似的錯誤訊息，刪除指令可以改用 image 名稱試試。

```
root@greencloud-us-1TB:~/watchtower# docker images
REPOSITORY                         TAG       IMAGE ID       CREATED         SIZE
mysteriumnetwork/myst              latest    5c613786d102   39 hours ago    53.3MB
presearch/node                     latest    27216957eb08   10 days ago     69.8MB
storjlabs/storagenode              latest    0ac3b4808897   3 weeks ago     124MB
lscr.io/linuxserver/transmission   latest    8cad68f9dac4   7 months ago    95.7MB
containrrr/watchtower              latest    333de6ea525a   8 months ago    16.9MB
jellyfin/jellyfin                  latest    0aa773b67433   13 months ago   717MB
presearch/auto-updater             latest    dd78a816fb76   17 months ago   16.4MB    <===
containrrr/watchtower              <none>    dd78a816fb76   17 months ago   16.4MB  <===
storjlabs/watchtower               latest    6af6621e20c1   2 years ago     14.3MB
nate/dockviz                       latest    93b5259c1e18   4 years ago     6.61MB


root@greencloud-us-1TB:~/watchtower# docker rmi dd78a816fb76
Error response from daemon: conflict: unable to delete dd78a816fb76 (must be forced) - image is referenced in multiple repositories


root@greencloud-us-1TB:~/watchtower# docker rmi presearch/auto-updater containrrr/watchtower
Untagged: presearch/auto-updater:latest
Untagged: presearch/auto-updater@sha256:3283e0b5be326d77ff4f4e8b7a91d46aaa1d511c74877b5a32f161548812d00c
Untagged: containrrr/watchtower:latest
Untagged: containrrr/watchtower@sha256:bbf9794a691b59ed2ed3089fec53844f14ada249ee5e372ff0e595b73f4e9ab3
Deleted: sha256:333de6ea525af9137e1f14a5c1bfaa2e730adca97ab97f74d738dfa99967f14f
Deleted: sha256:f493af3d0a518d307b430e267571c926557c85222217a8707c52d1cf30e3577e
Deleted: sha256:62651dc7e144aa8c238c2c2997fc499cd813468fbdc491b478332476f99af159
Deleted: sha256:83fe5af458237288fe7143a57f8485b78691032c8c8c30647f8a12b093d29343
```

##### - 無法存取 localhost 網頁

如果 container 啟用一個本地端的網站，從 host 端無法直接使用 `http://localhost:XXX` 方式存取；改用 `http://host.docker.internal:XXX` 網址。

# Docker Compose

<p class="callout info">NOTE: 新版指令改成 `docker compose`。</p>

<p class="callout info">NOTE: 服務一旦佈署完成，docker-compose.yml 的路徑如果有變更，就不能繼續使用指令 docker-compose 來管理 container，不過已經啟動的服務運行不會影響，但關閉後就無法再被啟動。</p>

新增與啟動所有應用服務

```bash
# For all services
docker-compose up -d

# For specified service
docker-compose up -d <service-name>
```

Build the image of the service

```
docker-compose build <service-name>
docker-compose up --rebuild <service-name>
```

目前的應用服務狀態

```
docker-compose ps
```

重啟所有應用服務但不要刪除所屬 container

```bash
docker-compose stop
docker-compose start 
```

> 如果 container 屬性沒有異動，只是所屬的服務需要重啟載入新設定，可以使用這。

停止所有應用服務並刪除所屬 container

```bash
# For all services
docker-compose down

# For a specified service
docker-compose stop <service-name>
docker-compose rm -f <service-name>
```

> NOTE:
> 
> \- 如果有修改 docker-compose.yml，必須使用 down 關閉服務後，用 up -d 重新啟動。
> 
> \- 停止服務後，重新啟動只能使用 docker-compose up -d

檢視服務啟動失敗的日誌

```
docker-compose logs
```

執行某個應用服務的特定程序

```
docker-compose exec <service-name> sh -c "pwd"
```

環境變數檔 `--env-file`

```bash
docker compose --env-file .env up -d
```

#### Labels

- 用途：如果要管理上百個 continers 時，標籤功能可用客製標籤來標記 container，以便日常管理維護。有些外部系統，例如 Prometheus，也會支援這個 label 功能。
- 用法：`--label my-label=my-value`
- 技巧：標籤的命名方式建議使用 com.myorg.XX 的格式，myorg 替換成組織的代名。
- 更多資訊：[How I Use Docker Labels and Compose Tricks to Organize 75+ Containers - Virtualization Howto](https://www.virtualizationhowto.com/2025/07/how-i-use-docker-labels-and-compose-tricks-to-organize-75-containers/)

docker-compose.yml

```yaml
services:
  web:
    image: myorg/web:2.3.1
    labels:
      com.myorg.project: payment-api
      com.myorg.environment: production
      com.myorg.team: billing
      com.myorg.service-type: backend
      com.myorg.version: 2.3.1
  redis:
    image: redis:6.2
    labels:
      com.myorg.project: payment-api
      com.myorg.environment: production
      com.myorg.team: billing
      com.myorg.service-type: cache
      com.myorg.version: "6.2"
```

使用 --filter 來過濾

```bash
docker ps --filter "label=com.myorg.project=payment-api"
docker inspect --format='{{json .Config.Labels}}' $(docker ps -q)
docker ps --filter label=com.myorg.environment=production --format "{{.Names}}: {{.Label \"com.myorg.version\"}}"
```

#### .env

敏感性的資料如密碼、API Token，建議使用 .env 檔案來儲存。

```yaml
    # Make a '.env' file in the same directory.
    env_file:
      - .env
```

.env

```
MYSQL_ROOT_PASSWORD=supersecurepassword
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=anothersecurepassword
```

docker-compose.yml

```yaml
version: "3.9"
services:
  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
```

```yaml
  frappe:
    image: frappe/bench:latest
    command: bash /workspace/init.sh
    environment:
      - SHELL=/bin/bash
    working_dir: /home/frappe
    volumes:
      - .:/workspace
    ports:
      - 8000:8000
      - 9000:9000
```

#### HealthCheck

##### ping

```yaml
    healthcheck:
      test: ping -c 1 www.google.com || exit 1
      interval: 20s
      timeout: 10s
      retries: 5
```

##### environment + healthcheck

```yaml
services:
  db:
    image: pgvector/pgvector:0.6.2-pg15
    restart: always
    ports:
      - '5432:5432'
    environment:
      POSTGRES_USER: talkdai
      POSTGRES_PASSWORD: talkdai
      POSTGRES_DB: talkdai
    volumes:
       - ./etc/db-extensions.sql:/docker-entrypoint-initdb.d/db-extensions.sql
    healthcheck:
      test: ["CMD", "pg_isready", "-d", "talkdai", "-U", "talkdai"]
      interval: 10s
      timeout: 5s
      retries: 5
  dialog:
    image: ghcr.io/talkdai/dialog:latest
    volumes:
      - ./data/:/app/data/
    ports:
      - '8000:8000'
    depends_on:
      db:
        condition: service_healthy
    environment:
      - PORT=8000
      - DATABASE_URL=postgresql://talkdai:talkdai@db:5432/talkdai
      - OPENAI_API_KEY=sk-your-openai-api-key
      - STATIC_FILE_LOCATION=/app/static
      - DIALOG_DATA_PATH=../data/your.csv
      - PROJECT_CONFIG=../data/your.toml
```

##### healthcheck(web) + build + networks

```yaml
networks:
  net:
    driver: bridge

services:
  server:
    image: server
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      # Be aware that indexed data are located in "/chroma/chroma/"
      # Default configuration for persist_directory in chromadb/config.py
      # Read more about deployments: https://docs.trychroma.com/deployment
      - chroma-data:/chroma/chroma
    command: "--workers 1 --host 0.0.0.0 --port 8000 --proxy-headers --log-config chromadb/log_config.yml --timeout-keep-alive 30"
    environment:
      - IS_PERSISTENT=TRUE
      - CHROMA_SERVER_AUTHN_PROVIDER=${CHROMA_SERVER_AUTHN_PROVIDER}
      - CHROMA_SERVER_AUTHN_CREDENTIALS_FILE=${CHROMA_SERVER_AUTHN_CREDENTIALS_FILE}
      - CHROMA_SERVER_AUTHN_CREDENTIALS=${CHROMA_SERVER_AUTHN_CREDENTIALS}
      - CHROMA_AUTH_TOKEN_TRANSPORT_HEADER=${CHROMA_AUTH_TOKEN_TRANSPORT_HEADER}
      - PERSIST_DIRECTORY=${PERSIST_DIRECTORY:-/chroma/chroma}
      - CHROMA_OTEL_EXPORTER_ENDPOINT=${CHROMA_OTEL_EXPORTER_ENDPOINT}
      - CHROMA_OTEL_EXPORTER_HEADERS=${CHROMA_OTEL_EXPORTER_HEADERS}
      - CHROMA_OTEL_SERVICE_NAME=${CHROMA_OTEL_SERVICE_NAME}
      - CHROMA_OTEL_GRANULARITY=${CHROMA_OTEL_GRANULARITY}
      - CHROMA_SERVER_NOFILE=${CHROMA_SERVER_NOFILE}
    restart: unless-stopped # possible values are: "no", always", "on-failure", "unless-stopped"
    ports:
      - "8000:8000"
    healthcheck:
      # Adjust below to match your container port
      test: [ "CMD", "curl", "-f", "http://localhost:8000/api/v1/heartbeat" ]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - net

volumes:
  chroma-data:
    driver: local
```

#### vlan network

用途：Container 設定固定 IP

新增 macvlan 網路

```bash
docker network create -d macvlan \
    --subnet=192.168.0.0/24 \
    --gateway=192.168.0.1 \
    --ip-range=192.168.0.100/28 \
    -o parent=eth0 vlan
```

編輯 compose.yml

```
services:
  windows:
    container_name: windows
    ..<snip>..
    networks:
      vlan:
        ipv4_address: 192.168.0.100

networks:
  vlan:
    external: true
```

#### Command

```yaml
  frappe:
    image: frappe/bench:latest
    command: bash /workspace/init.sh
    environment:
      - SHELL=/bin/bash
    working_dir: /home/frappe
    volumes:
      - .:/workspace
    ports:
      - 8000:8000
      - 9000:9000
```

```yaml
version: "3.7"
services:
  mariadb:
    image: mariadb:10.8
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --skip-character-set-client-handshake
      - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6
    environment:
      MYSQL_ROOT_PASSWORD: 123
    volumes:
      - mariadb-data:/var/lib/mysql
```

#### Misc

- `pull_policy: always` 重啟 container 時強制拉取最新的 image。

# Installation

#### Docker Compose

Updated: 新版 Docker Compose 已經整合進 Docker 核心程式

安裝方式

```shell
sudo apt-get install docker-compose-plugin
```

使用方式

```shell
docker compose version
```

The latest download: [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/)

```shell
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# If you can't run the command, creating a symblic link as follows.
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

docker-compose --version
```

##### ARM64 Linux

```shell
# Download: https://github.com/docker/compose/releases
sudo curl -L --fail https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-aarch64 -o /usr/local/bin/docker-compose
sudo chmod 0755 /usr/local/bin/docker-compose
```

#### Docker

Latest: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/)

##### Fedora 41

```bash
sudo dnf -y install dnf-plugins-core
sudo dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo

sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

##### Ubuntu 20.04/22.04

```shell
sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
    
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
```

##### Debian

```shell
sudo apt-get update

# Uninstall all conflicting packages
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-doc podman-docker containerd runc | cut -f1)

# Install the dependencies
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl gnupg \
    lsb-release
    

# Add Docker's GPG key
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Set up the repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Verify that Docker is installed correctly.
sudo dokcer version
sudo docker run hello-world

```

##### RedHat 8  


> RHEL 8 只有 IBM Z(s390x) 架構才有支援 Docker，如果是 x86\_64 可以使用 CentOS 版本來安裝。
> 
> 預設不支援 docker-ce 安裝，必須手動加上套件庫位址

系統更新 (需有訂閱並完成註冊 RHN)

```shell
dnf update
```

手動新增 docker-ce 套件庫位址

編輯 /etc/yum.repos.d/docker-ce.repo

```
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://download.docker.com/linux/centos/$releasever/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://download.docker.com/linux/centos/gpg
```

或直接執行

```shell
dnf install yum-utils
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
```

更新 docker-ce 相依性套件

```shell
dnf update
```

> TIP: 如果遇到一些套件相依性的錯誤，執行下一步的移除衝突套件後，再執行一次 `dnf update`。

移除不用的衝突套件

```shell
dnf remove podman buildah
```

安裝 docker-ce

```shell
dnf install docker-ce docker-ce-cli containerd.io
```

啟動與自動啟動

```shell
systemctl start docker.service
systemctl enable docker.service
```

檢查 docker 版本

```
[root@mydocker ~]# docker version
Client: Docker Engine - Community
 Version:           20.10.14
 API version:       1.41
 Go version:        go1.16.15
 Git commit:        a224086
 Built:             Thu Mar 24 01:47:44 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.14
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.15
  Git commit:       87a90dc
  Built:            Thu Mar 24 01:46:10 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.5.11
  GitCommit:        3df54a852345ae127d1fa3092b95168e4a88e2f8
 runc:
  Version:          1.0.3
  GitCommit:        v1.0.3-0-gf46b6ba
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

\[Optional\] non-root 用戶帳戶

```shell
usermod -aG docker $USER
```

##### Raspberry Pi OS

```bash
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker Pi
docker version
docker info
docker run hello-world
```

#### Rootless Mode

##### Ubuntu 22.04

Stop system-wide Docker daemon

```bash
sudo systemctl disable --now docker.service docker.socket
```

Install

```bash
sudo apt-get install docker-ce-rootless-extras uidmap
dockerd-rootless-setuptool.sh install
docker info
```

Optional: Configure the systemd

```bash
systemctl --user start docker
systemctl --user enable docker
sudo loginctl enable-linger $(whoami)
```

- The socket path is set to `$XDG_RUNTIME_DIR/docker.sock` by default. `$XDG_RUNTIME_DIR` is typically set to `/run/user/$UID`.
- The data dir is set to `~/.local/share/docker` by default. The data dir should not be on NFS.
- The daemon config dir is set to `~/.config/docker` by default. This directory is different from `~/.docker` that is used by the client.

Optional: Client

```bash
# Optional #1: with socket path
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker run -d -p 8080:80 nginx
```

```bash
# Optional #2: with CLI context
docker context use rootless
docker run -d -p 8080:80 nginx
```

# Dockerfile

##### Build Image

- Docker image 瘦身工具: [dive](https://github.com/wagoodman/dive)

Basic build command

```shell
cd /path/to/Dockerfile
docker build -t your-tag/your-name .
```

##### Set the version

More details: [docker-bookstack/Dockerfile at master · linuxserver/docker-bookstack · GitHub](https://github.com/linuxserver/docker-bookstack/blob/master/Dockerfile)

```
FROM ghcr.io/linuxserver/baseimage-alpine-nginx:3.17

# set version label
ARG BUILD_DATE
ARG VERSION
ARG BOOKSTACK_RELEASE
LABEL build_version="Linuxserver.io version:- ${VERSION} Build-date:- ${BUILD_DATE}"
LABEL maintainer="homerr"
```

使用方法:

Build the image

```bash
docker build \
  --build-arg BUILD_DATE=$( date +"%FT%T%z" ) \
  --build-arg VERSION=v22.07.3-ls36 \
  --no-cache \
  --pull \
  -t lscr.io/linuxserver/bookstack:latest .
```

Get the version of container

```bash
docker inspect -f '{{ index .Config.Labels "build_version" }}' <container-name>
```

##### Examples

Chroma DB

```haskell
FROM python:3.11-slim-bookworm AS builder
ARG REBUILD_HNSWLIB
RUN apt-get update --fix-missing && apt-get install -y --fix-missing \
    build-essential \
    gcc \
    g++ \
    cmake \
    autoconf && \
    rm -rf /var/lib/apt/lists/* && \
    mkdir /install

WORKDIR /install

COPY ./requirements.txt requirements.txt

RUN pip install --no-cache-dir --upgrade --prefix="/install" -r requirements.txt
RUN if [ "$REBUILD_HNSWLIB" = "true" ]; then pip install --no-binary :all: --force-reinstall --no-cache-dir --prefix="/install" chroma-hnswlib; fi

FROM python:3.11-slim-bookworm AS final

RUN mkdir /chroma
WORKDIR /chroma

COPY --from=builder /install /usr/local
COPY ./bin/docker_entrypoint.sh /docker_entrypoint.sh
COPY ./ /chroma

RUN apt-get update --fix-missing && apt-get install -y curl && \
    chmod +x /docker_entrypoint.sh && \
    rm -rf /var/lib/apt/lists/*

ENV CHROMA_HOST_ADDR "0.0.0.0"
ENV CHROMA_HOST_PORT 8000
ENV CHROMA_WORKERS 1
ENV CHROMA_LOG_CONFIG "chromadb/log_config.yml"
ENV CHROMA_TIMEOUT_KEEP_ALIVE 30

EXPOSE 8000

ENTRYPOINT ["/docker_entrypoint.sh"]
CMD [ "--workers ${CHROMA_WORKERS} --host ${CHROMA_HOST_ADDR} --port ${CHROMA_HOST_PORT} --proxy-headers --log-config ${CHROMA_LOG_CONFIG} --timeout-keep-alive ${CHROMA_TIMEOUT_KEEP_ALIVE}"]
```

Python App

```yaml
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]
```

##### Learning

- [Writing An Optimized Dockerfile](https://www.codewall.co.uk/writing-an-optimized-dockerfile/)
- [What is the Difference Between COPY and ADD Instructions in Dockerfile](https://linuxhandbook.com/dockerfile-copy-add-difference/)
- [Best practices for writing Dockerfiles](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)
- [How to Build Slim and Fast Docker Images with Multi-Stage Builds](https://www.freecodecamp.org/news/build-slim-fast-docker-images-with-multi-stage-builds/)
- [Docker Build Tutorial: Learn Contexts, Architecture, and Performance Optimization Techniques](https://www.freecodecamp.org/news/docker-build-tutorial-learn-contexts-architecture-and-performance-optimization-techniques/)
- Node.js 
    - [How to Containerize a Node.js Application Using Docker – A Beginner's Guide](https://www.freecodecamp.org/news/containerize-a-nodejs-application-using-docker/)

# Learning

##### 教學文章

Basic

- \[中文\] [Docker in Production](https://github.com/ianchen0119/DockerInProduction)
- [The Docker Handbook – Learn Docker for Beginners](https://www.freecodecamp.org/news/the-docker-handbook/)
- [Introduction to Docker eBook](https://github.com/bobbyiliev/introduction-to-docker-ebook)

Advanced

- [How to SSH into a Docker Container](https://linuxhandbook.com/ssh-into-container/)
- [Monitoring Docker Containers With Grafana Using Dockprom](https://linuxhandbook.com/docker-container-monitoring/)
- [How to Set Up Remote Access to Docker Daemon \[Detailed Guide\]](https://linuxhandbook.com/docker-remote-access/)
- [How to deploy on remote Docker hosts with docker-compose](https://www.docker.com/blog/how-to-deploy-on-remote-docker-hosts-with-docker-compose/)
- [Where are Docker Images, Containers and Volumes Stored on the Linux Host System?](https://linuxhandbook.com/docker-image-container-location/)
- [How To Analyze And Compare Container Images Using Container-diff](https://ostechnix.com/easily-quickly-compare-container-images/)
- [How to Set Docker Memory and CPU Usage Limit](https://phoenixnap.com/kb/docker-memory-and-cpu-limit)
- [找回那些被 Docker 吃掉的磁碟空間. 如果你是 Docker/Kubernetes… | by Ian Chen | Starbugs Weekly 星巴哥技術專欄 | Medium](https://medium.com/starbugs/%E6%89%BE%E5%9B%9E%E9%82%A3%E4%BA%9B%E8%A2%AB-docker-%E5%90%83%E6%8E%89%E7%9A%84%E7%A3%81%E7%A2%9F%E7%A9%BA%E9%96%93-6912cdb24dc0)
- [Top Docker Performance Tweaks on Low-Power Hardware - Virtualization Howto](https://www.virtualizationhowto.com/2025/05/top-docker-performance-tweaks-on-low-power-hardware/)
- [10 Common Docker Container Mistakes and How to Avoid Them - Virtualization Howto](https://www.virtualizationhowto.com/2025/08/10-common-docker-container-mistakes-and-how-to-avoid-them/)

##### Backup

Resilio Sync

- [Backup Docker Containers using Resilio Sync](https://thehomelab.wiki/books/docker/page/backup-docker-containers-using-resilio-sync)
- \[Video\] [Backup Docker Container Files with Resilio Sync](https://www.youtube.com/watch?v=4hda0ib4DwQ)

##### Network

- [How to Create and Use MacVLAN Network in Docker](https://www.linuxtechi.com/create-use-macvlan-network-in-docker/)
- [Gluetun：讓Docker容器走VPN連線，沒網路就斷線，使用教學 · Ivon的部落格 (ivonblog.com)](https://ivonblog.com/posts/gluetun-vpn-docker/)
- [Docker Networking Tutorial: Bridge vs. Macvlan vs. Overlay for Home Labs - Virtualization Howto](https://www.virtualizationhowto.com/2025/07/docker-networking-tutorial-bridge-vs-macvlan-vs-overlay-for-home-labs/)

##### Container

- [What you need to know about containers for Python](https://opensource.com/article/21/11/containers-python?sc_cid=70160000001273HAAQ)

##### Docker Security Essentials

Download: [Linode\_eBook\_HackerSploit\_DockerSecurityEssentials.pdf](https://osslab.tw/attachments/63)

<span style="font-family:'system-ui', '---apple-system', BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">Content is structured and organized as follows:</span>

<div class="fl-col-group fl-node-60faf099e696a fl-col-group-nested" id="bkmrk-in-the-docker-platfo"><div class="fl-col fl-node-60faf099e6a4c"><div class="fl-col-content fl-node-content"><div class="fl-module fl-module-rich-text fl-node-60faf084dad7b fl-module--grid fl-module--span-10"><div class="fl-module-content fl-node-content"><div class="fl-rich-text">- In **The Docker Platform** section, we will begin the process by explaining the various components that make up the Docker platform.
- In the **Auditing Docker Security** section, we will explore the process of performing a security audit of the Docker platform. An audit identifies vulnerabilities in the configuration of the components that make up the platform.
- In the next two sections, we will begin the process of securing the Docker host and the Docker daemon to ensure that we have a secure base to operate from: 
    - **Securing the Docker Host**
    - **Securing the Docker Daemon**
- <span class="s1">The remaining sections of the guide will conclude by taking a look at the various ways of securing containers and the process of building secure Docker images:</span>
    - <span class="s1">**Container Security Best Practices**</span>
    - <span class="s1">**Controlling Container Resource Consumption with Control Groups (cgroups)**</span>
    - <span class="s1">**Implementing Access Control with AppArmor**</span>
    - <span class="s1">**Limiting Container System Calls with seccomp** </span>
    - <span class="s1">**Vulnerability Scanning for Docker Containers**</span>
    - <span class="s1">**Building Secure Docker Images** </span>
- <span class="s1">[Docker Makes Enterprise-Grade Hardened Images Free for All Developers](https://itsfoss.com/news/docker-hardened-images-open-sourced/)</span>

</div></div></div></div></div></div>##### Docker Monitoring

- \[Video\] [Best Docker Container Monitoring Tools - Free and open source](https://www.youtube.com/watch?v=zxAmqY63eJE)
- [Lazydocker](https://github.com/jesseduffield/lazydocker) - Docker UI 
    - \[Video\] [Your Minimalist Docker UI - Lazydocker](https://www.youtube.com/watch?v=lu8edvTDUvI)
- [Dozzle](https://dozzle.dev/) - Real-time logging and monitoring for Docker in the browser 
    - GitHub: [https://github.com/amir20/dozzle](https://github.com/amir20/dozzle)
- [dockprom](https://github.com/stefanprodan/dockprom) - A monitoring solution for Docker hosts and containers with Prometheus, Grafana, cAdvisor, NodeExporter and alerting with AlertManager.
- [ctop](https://github.com/bcicen/ctop) - Top-like interface for container metrics.
- [oxker](https://github.com/mrjackwills/oxker) - A simple tui to view &amp; control docker containers.
- [LoggiFly](https://github.com/clemcer/loggifly) - 監視 Container Log 並發出告警

##### Other Tools

- [dockerc](https://github.com/NilsIrl/dockerc) - compile docker images to standalone portable binaries

# SWAG - Secure Web Application Gateway

[SWAG](https://hub.docker.com/r/linuxserver/swag) is a rebirth of the [letsencrypt docker image](https://hub.docker.com/r/linuxserver/letsencrypt), a full fledged web server and reverse proxy that includes Nginx, Php7, Certbot (Let's Encrypt client) and Fail2ban.

##### Tutorials

- [Introducing SWAG - Secure Web Application Gateway](https://blog.linuxserver.io/2020/08/21/introducing-swag/)
- [SWAG setup](https://docs.linuxserver.io/general/swag)

# Transfer/Move Docker

##### Sample: osslab-dekiwiki

```shell
docker run -d -p 8880:80 \
-v /etc/localtime:/etc/localtime:ro \
-v $PWD/vol/data:/data \
-v $PWD/vol/var-lib-mysql:/var/lib/mysql \
-v $PWD/vol/var-log-httpd:/var/log/httpd \
-v $PWD/vol/var-log-dekiwiki:/var/log/dekiwiki \
-v $PWD/vol/var-www-dekiwiki-attachments:/var/www/dekiwiki/attachments \
-v $PWD/vol/var-www-dekiwiki-bin-cache:/var/www/dekiwiki/bin/cache \
--name $NAME \
osslab-dekiwiki \
/bin/bash
```

##### Using Save Command

Save Image

```shell
# osslab-dekiwiki is image name
docker save osslab-dekiwiki > osslab-dekiwiki.tar

ls -l
```

```
-rw-r--r-- 1 root root 2519701504 Feb 27 19:19 osslab-dekiwiki.tar
```

Load Image

```
docker load < osslab-dekiwiki.tar
```

##### Using Export Command

Run the instance in detach mode

```shell
docker run -it --detach --name osslab-mig osslab-dekiwiki

docker ps
```

```
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                            NAMES
673f45c27a8b        osslab-dekiwiki     "/bin/sh -c /startup…"   5 seconds ago       Up 4 seconds        80/tcp                           osslab-mig
```

Export Docker Container

```shell
docker export 673f45c27a8b > osslab-mig.tar

ls -l
```

```
-rw-r--r-- 1 root root 1143085568 Feb 27 14:21 osslab-mig.tar
```

Import Docker Container to the target machine

```shell
tar -c osslab-mig.tar | docker import - osslab-dekiwiki
```

##### Move Container

```bash
## Stop the container
docker stop <container-name>

## Save container image
docker commit <container-name> mycontainerimage
docker save mycontainerimage | gzip > mycontainerimage.tar.gz

## Load container image to destination host
gunzip -c mycontainerimage.tar.gz | docker load 

## Transfer image without creating a file
docker save mycontainerimage | gzip | ssh root@203.0.113.1 'gunzip | docker load'
```

> TIP:  
> 執行 exit 可以離開目前的 container，回到原先的 Linux
> 
> 一旦離開 container，所有之前做過的變更，將全部失效，如果要保留做過的變更，必須使用 commit 產生一個新的 image。

##### Backup &amp; Restore Volume

Backup volume

```shell
# Backup
cd /root/osslab
docker stop osslab-dekiwiki
docker rm osslab-dekiwiki
tar czf osslab-val.tar.gz vol

# Restore to target machine
cd /root/osslab
tar xpzf osslab-val.tar.gz
```

##### FAQ

Q: Import 完成，但無法啟動 container。

> Error response from daemon: OCI runtime create failed: container\_linux.go:380: starting container process caused: exec: "/bin/sh": stat /bin/sh: no such file or directory: unknown  
> Error: failed to start containers: osslab

Solution: 不要使用 Export-Import 改成 Save-Load 方式移轉。

Q: 執行 docker load 發生錯誤。

> open /var/lib/docker/tmp/docker-import-863867335/bin/json: no such file or directory

Solution: 只有 Save 的 image 檔才能執行 `docker load`；Export 的 image 檔只能用 `docker import`。

# Docker Management Tools

#### Portainer

[https://www.portainer.io/](https://www.portainer.io/)

A centralized service delivery platform for containerized apps.

Portainer can be deployed on top of any K8s, Docker or Swarm environment. It works seamlessly in the cloud, on prem and at the edge to give you a consolidated view of all your containers.

Portainer is available in 2 versions: Business Edition (BE) and Community Edition (CE). Portainer BE is a fully supported, service delivery platform suitable for use organization-wide, whilst CE is open source and suitable for use by small, self-supporting teams.

##### Installation

- [https://docs.portainer.io/v/ce-2.11/start/install](https://docs.portainer.io/v/ce-2.11/start/install)
- \[Video\] [https://www.youtube.com/watch?v=DGw6P5Lkj-U](https://www.youtube.com/watch?v=DGw6P5Lkj-U)

#### Yacht

[Yacht](https://github.com/SelfhostedPro/Yacht) - Yacht is a container management UI with a focus on templates and 1-click deployments.

- \[Video\] [Yacht - an Open Source, Self Hosted, Modern, Web GUI for Docker Management similar to Portainer.](https://www.youtube.com/watch?v=eTQ2iB-hjkk)
- [Yacht ～ 簡易Docker圖形管理界面，透過網頁寫docker-compose · Ivon的部落格 (ivonblog.com)](https://ivonblog.com/posts/yacht-docker-web-interface/)

#### Dockge

Self-hosted - Docker compose.yaml - Stack-oriented Manager

- [Dockge](https://dockge.kuma.pet/)
- GitHub: [https://github.com/louislam/dockge](https://github.com/louislam/dockge)

#### Lazydocker

A simple terminal UI for both docker and docker-compose, written in Go with the gocui library.

- GitHub: [https://github.com/jesseduffield/lazydocker](https://github.com/jesseduffield/lazydocker)

# Watchtower

GitHub: [https://github.com/containrrr/watchtower](https://github.com/containrrr/watchtower) (已終止維護自 2025/12/17)

##### Setup

run.sh

```bash
docker stop watchtower-myst-presearch-storj
docker rm watchtower-myst-presearch-storj

docker pull containrrr/watchtower
docker run -d \
    --name watchtower-myst-presearch-storj \
    -v /var/run/docker.sock:/var/run/docker.sock \
    containrrr/watchtower \
    myst presearch-node storagenode
docker ps

```

##### WUD (Alternative)

- [How to Keep Containers Up-to-Date with What’s Up Docker (WUD)](https://linuxiac.com/how-to-keep-containers-up-to-date-with-whats-up-docker-wud/)

##### Tutorials

- [How To Automatically Update Running Docker Containers Using Watchtower](https://ostechnix.com/automatically-update-running-docker-containers/)

# 進階應用

##### 存取 Container 網路

有個 Container 服務只需要與同個 Docker Network 的其他 Container 做通訊，平常不需要對外開放通訊埠，只有在開發或 Debug 時才需要從外部存取這個 Container 的網路，但又不想每次都要重啟 container 來開啟關閉需要的 port。

使用一個 socat 的 forwarder container，將外部 port 導入內部目的 container 的 port。

[![socat-forwarder.png](https://osslab.tw/uploads/images/gallery/2023-01/scaled-1680-/socat-forwarder.png)](https://osslab.tw/uploads/images/gallery/2023-01/socat-forwarder.png)

```bash
# 以 Nginx Web 為例
$ docker run -d --name target nginx

$ TARGET_IP=$(
  docker inspect \
    -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
    target
)

$ NETWORK=$(
  docker container inspect \
    -f '{{range $net,$v := .NetworkSettings.Networks}}{{printf "%s" $net}}{{end}}' \
    target
)

$ docker run -d \
  --publish ${HOST_PORT}:${TARGET_PORT} \
  --network ${NETWORK} \
  --name forwarder nixery.dev/socat \
    socat TCP-LISTEN:${TARGET_PORT},fork TCP-CONNECT:${TARGET_IP}:${TARGET_PORT}
    
$ curl localhost:${TARGET_PORT}
```

##### 不停機做 Container 更新  


- [Updating Docker Containers With Zero Downtime (linuxhandbook.com)](https://linuxhandbook.com/update-docker-container-zero-downtime/)

# Linux Distros for Running Container

- [5 Best Lightweight Linux Distros for Running Containers (tecmint.com)](https://www.tecmint.com/linux-distributions-for-containers/)

#### Photon OS

- [https://vmware.github.io/photon/docs/](https://vmware.github.io/photon/docs/)
- [Downloading Photon OS · vmware/photon Wiki · GitHub](https://github.com/vmware/photon/wiki/Downloading-Photon-OS)

Default account credentials:

- username: `root`
- password: `changeme`

Run Docker

```bash
# To run Docker from the command prompt
systemctl start docker

# To ensure Docker daemon service runs on every subsequent VM reboot
systemctl enable docker
```

#### Fedora CoreOS

- [The container optimized OS | The Fedora Project](https://fedoraproject.org/coreos/)
- Doc: [Fedora CoreOS Documentation :: Fedora Docs](https://docs.fedoraproject.org/en-US/fedora-coreos/)
- [Proxmox Containers with Fedora CoreOS Install - Virtualization Howto](https://www.virtualizationhowto.com/2024/02/proxmox-containers-with-fedora-coreos-install/)

# Dockerize Custom Application

##### Tutorials

- [Dockerize a Python application - DEV Community](https://dev.to/francescoxx/dockerize-a-python-application-1olg)
- [DEV Community](https://dev.to/search?q=dockerize%20)

# Container Registry

Docker 容器倉庫

##### Docker Hub

- Doc: [https://docs.docker.com/](https://docs.docker.com/)
- Login: `docker login -u <username>`

##### Azure Container Registry (ACR)

- Doc: [https://docs.azure.cn/en-us/container-registry/](https://docs.azure.cn/en-us/container-registry/)
- Login: `az acr login --name <registry-name>`

##### Google Container Registry (GCR)

##### AWS Elastic Container Registry (ECR)