Docker

【上課筆記】【hiskio】Docker 完全實作!結合 3 大語言掌握容器實務應用

上課講義

Docker 完全實作 – Dropbox Paper


 

image-1642476831997.png

nocopy : 存在檔案不覆蓋

 

Docker 完全實作

man teacher 讓我們先複習一下 Docker 的安裝

Windows 上的 Docker 環境建置

https://docs.docker.com/desktop/windows/install/
https://www.youtube.com/watch?v=lIkxbE_We1I&
https://youtu.be/lIkxbE_We1I

Mac 上的 Docker 環境建置

https://docs.docker.com/desktop/mac/install/
police car light Apple M1的限制

Linux 上的 Docker 環境建置

https://docs.docker.com/engine/install/ubuntu/

實務上需要注意的事

CPU/Memory資源
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205117720_image.png
掛載目錄
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205302746_image.png
Buildkit
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634205247210_image.png
回復初始設定
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634207204490_image.png

whale 工欲善其事,必先利其器:Docker 技術快覽

Docker 常用指令  

s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634184121714_image.png
執行容器
# 跑一個新的apache容器
docker run httpd:alpine
# 跑一個背景執行的apache容器,並取名為apache
docker run -d --name apache httpd:alpine 
# 跑一個redis容器,啟動虛擬終端機,進入互動模式,在容器中執行sh
docker run -it --rm redis:alpine sh
列出容器
# 列出所有執行中容器
docker ps
# 列出所有容器
docker ps -a
停止容器
# 停止apache 容器
docker stop apache
重新啟動容器
# 重新啟動apache
docker restart apache
Kill容器
#強制停止容器
docker kill apache
移除容器
#移除apache
docker rm apache
#檢查所有容器
docker ps -a
#移除所有容器
docker rm -f $(docker ps -a -q)
複製檔案
# 建立一個hello.html
echo "hello" > hello.html
# 建立apache
docker run -d --name apache httpd:alpine 
# 複製檔案至apache 容器中
docker cp hello.html apache:/usr/local/apache2/htdocs/
# 複製apache 容器中的檔案至本機
docker cp apache:/usr/local/apache2/htdocs/index.html ./index.html
在容器內執行程式
#在容器中執行shell
docker exec -it apache sh
掛載目錄
#把hello.html掛成index.html
docker run -d --name apache2 -v $PWD/hello.html:/usr/local/apache2/htdocs/index.html httpd:alpine
# copy出來驗證內容是否一樣
docker cp apache2:/usr/local/apache2/htdocs/index.html ./index.html
diff index.html hello.html
建立外部連線
#把「本機的80埠」對應到「容器中的80埠」
docker run -d -v $PWD/hello.html:/usr/local/apache2/htdocs/index.html -p 80:80 httpd:alpine
http://127.0.0.1:80
查看容器狀態
# 查看 CPU、記憶體與網路用量
docker stats
查看容器內的行程
# 查看容器內部的行程
docker top apache
docker top apache2
查看容器Logs
#查看log
docker logs apache
#持續查看
docker logs -f apache2
建立映像檔
# 於當前目錄,按Dockerfile.test中的指令,建立test:v1的映像檔
docker build . -f Dockerfile.test -t test:v1
拉取與推送映像檔
#從docker hub下載 node的image
docker pull node:alpine
#將下來的image,再給於新的標籤
docker tag node:alpine macchiang/node:alpine
#推送新的標籤至私人倉庫
docker push macchiang/node:alpine
清除映像檔與容器
#停掉全部container
docker stop $(docker ps -a -q)
#刪除所有沒有使用的container與image
docker system prune
#刪除所有映像檔
docker rmi $(docker images -a -q)
#清除volume
docker volume rm $(docker volume ls)
#刪除所有沒有使用的container、image及所有volume
docker system prune -a -f --volumes

Dockerfile 起手式

基本組成
# Base Image - Official Alpine
FROM alpine
 
LABEL "maintainer"="macchiang@gmail.com"
 
# Upgrade existing packages in the base image
RUN apk --no-cache upgrade
 
# Install apache from packages with out caching install files
RUN apk add --no-cache apache2
 
# Open port for httpd access
EXPOSE 80
 
# Run httpd in foreground
CMD ["-D","FOREGROUND"]
 
# Start httpd when container runs
ENTRYPOINT ["/usr/sbin/httpd"]
建立docker image
docker build . -t apache:v1
複寫CMD
#直接複寫CMD,改為顯示版號
docker run --rm apache:v1 -v
ENV環境變數
FROM alpine
ENV NODE_ENV="development"
ARG傳入參數
FROM alpine
ARG NODE_ARG
ENV NODE_ENV="${NODE_ARG:-development}"
RUN echo "ARG=${NODE_ARG}, ENV=${NODE_ENV}"
CMD echo "ARG=${NODE_ARG}, ENV=${NODE_ENV}"
# 傳入NODE_ARG
DOCKER_BUILDKIT=0 docker build --build-arg NODE_ARG=staging .
# 傳入NODE_ARG
DOCKER_BUILDKIT=0 docker build .
COPY與ADD 複製文件
#複製package.json到/app下
COPY package.json /app/
#複製所有file開頭的檔案到/app下,並改變檔案所屬用戶與群組
COPY --chown=user:group file* /app/
WORKDIR工作目錄
# hi.txt會在/目錄下
RUN echo "say hi" > hi.txt
WORKDIR /app
# world.txt會在/app的目錄下
RUN echo "hello" > world.txt
USER指定當前用戶
#新增group與user
RUN groupadd -r redis && useradd -r -g redis redis
#指定當前的user為redis
USER redis
#以redis的身份執行redis-server
RUN [ "redis-server" ]
HEALTHCHECK 健康檢查
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
  CMD curl -fs http://localhost/ || exit 1

打造最小的Docker映像檔 

多階段建置Multi-stage Build
# Stage 1 建置環境
FROM alpine AS build
RUN echo "hello" > mytest
 
# Stage 2 執行環境
FROM alpine  
COPY --from=build /mytest .
RUN cat /mytest
Distroless
https://github.com/GoogleContainerTools/distroless
https://github.com/GoogleContainerTools/distroless/tree/main/examples/go
main.go
package main
 
import "fmt"
 
func main() {
        fmt.Println("Hello, world!")
}
Dockerfile
FROM golang:1.12 as build-env
WORKDIR /go/src/app
COPY . /go/src/app
RUN go get -d -v ./...
RUN go build -o /go/bin/app
 
FROM gcr.io/distroless/base
COPY --from=build-env /go/bin/app /
CMD ["/app"]

建立多重系統架構的映像檔

不同執行環境的問題
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634268176860_image.png
使用Buildx建構多重系統架構的映像檔
https://docs.docker.com/desktop/multi-arch/
#確認目前的builder為何
docker buildx ls
#建立新的builder並切換過去
docker buildx create --name multiarch --use
light bulb 若在linux環境中建立builder的話,需要額外跑qemu-user-static容器,並將driver設為docker-container
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker buildx create --name multiarch --driver docker-container --use
#檢查目前的builder
docker buildx inspect --bootstrap
#建立支援amd64與arm64的image,並且推送至registry
docker buildx build . --platform linux/amd64,linux/arm64 --push -t hello:v1

orangutan 一次控制多個容器:Docker Compose

Docker Compose 的概念

s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634196692841_image.png

Docker Compose 文件格式

基本格式
預設文件名稱為 docker-compose.yml
version: "3"
services:
  webapp:
    #指定image
    image: httpd:alpine 
    ports:
      - "80:80"
    volumes:
      - "/data"
build 建立映像檔
version: '3'
services:
  webapp:
    #從Dockerfile-alternate建立映像檔
    build:
      context: .
      dockerfile: Dockerfile-alternate
      args:
        buildno: 1
https://docs.microsoft.com/zh-tw/visualstudio/docker/tutorials/use-docker-compose
https://docs.docker.com/compose/compose-file/compose-file-v3/

Docker Compose 命令參考

https://docs.docker.com/compose/reference/
執行docker compose
#前景執行
docker-compose up
#背景執行
docker-compose up -d
停止/啟動docker compose
docker-compose stop
移除docker compose
docker-compose down
#也同步移除volume
docker-compose down -v
列出containers
docker-compose ps
監看logs
docker-compose logs
#follow logs
docker-compose logs -f
執行程式
docker-compose exec webapp sh
顯示執行程序
docker-compose top

Docker Compose 環境變數

environment
environment:
  RACK_ENV: development
  SHOW: 'true'
  SESSION_SECRET:
environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET
env_file 
.env
VAR=hello
MYENV=development
env_file: .env
env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/runtime_opts.env

Docker Compose 健康檢查

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"]
  interval: 1m30s
  timeout: 10s
  retries: 3
  start_period: 40s

Docker Compose 起動順序

version: "3"
services:
  web:
    build: .
    ports:
      - "80:8000"
    depends_on:
      - "db"
    command: ["./wait-for-it.sh", "db:5432", "--", "python", "app.py"]
  db:
    image: postgres
hammer and pick 小工具

Docker Compose 磁碟掛載

types-of-mounts-volume.png
Long syntax
version: "3.9"
services:
  web:
    image: nginx:alpine
    volumes:
      - type: volume
        source: mydata
        target: /data
        volume:
          nocopy: true
      - type: bind
        source: ./static
        target: /opt/app/static
 
  db:
    image: postgres:latest
    volumes:
      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
      - "dbdata:/var/lib/postgresql/data"
 
volumes:
  mydata:
  dbdata:
Short syntax
volumes:
  # Just specify a path and let the Engine create a volume
  - /var/lib/mysql
 
  # Specify an absolute path mapping
  - /opt/data:/var/lib/mysql
 
  # Path on the host, relative to the Compose file
  - ./cache:/tmp/cache
 
  # User-relative path
  - ~/configs:/etc/configs/:ro
 
  # Named volume
  - datavolume:/var/lib/mysql

fireworks 如何導入Docker

Docker導入流程

  1. 確認應用程式使用的程式語言與版本
  2. 參考相同程式語言的Dockerfile寫法
  3. 列出應用程式相依的套件(包含系統、語言、字型等等)
  4. 列出應用程式的設定(環境變數、設定檔)
  5. 撰寫Dockerfile並測試
  6. 檢查是否有檔案不該加入映像檔中( .dockerignore)
  7. 列出應用程式相依的服務(Database、Redis)
  8. 確認服務啟動順序並撰寫Docker Compose yaml

實作:Node.js + MongoDB打造API服務

s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1634269999196_image.png
https://github.com/macchiang/node-express-mongo-example
透過 Dockerfile 部署 Node.js 環境 
# Base image
FROM node:16-alpine
 
RUN apk upgrade --no-cache
 
# Create app directory
RUN mkdir -p /app
WORKDIR /app
 
# Install app dependencies
COPY package.json package-lock.json ./
RUN npm install
 
# copy app source
COPY . /app
 
# service port
EXPOSE 3000
 
CMD [ "node", "index.js"]
Docker Compose YAML
version: "3.9"
services:
  express:
    build:
      context: ./
      dockerfile: Dockerfile
    image: express:latest
    ports:
      - 3000:3000
    depends_on:
      - mongo
  mongo:
    image: mongo:5
    ports:
      - 27017:27017
 
volumes:
    mongo_data:

house with garden 作業-health check的妙用

soccer ball Docker X Python實作:快速打造 LINE Bot

透過 Dockerfile 建立 Python 環境

確認python的image與版本
https://hub.docker.com/_/python
了解Dockerfile
FROM python:3-slim-bullseye
 
WORKDIR /usr/src/app
 
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
 
COPY . .
 
CMD [ "python", "./your-daemon-or-script.py" ]
 

Flask + Redis 打造計數器

https://github.com/macchiang/hit-counter
docker-compose.yml
version: '2'
services:
  myapp:
    build: .
    ports:
     - "80:5000"
  redis-lb:
    image: "redis:alpine"

構建 Django Web server

https://github.com/macchiang/django-todolist
https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Django/Introduction
先了解專案的安裝方式
Dockerfile
FROM python:3-slim-bullseye
 
WORKDIR /app
 
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
 
COPY todo/* .
RUN python manage.py migrate
CMD [ "python", "manage.py", "runserver" ]
docker-compose.yaml
version: '3.9'
services:
  myweb:
    build: .
    ports:
     - "8000:8000"

部署 LINE Bot 應用

https://github.com/macchiang/line-bot-tutorial
https://github.com/line/line-bot-sdk-python/tree/master/examples/simple-server-echo
在你開始之前
確保您具有以下內容:
建立LINE Bot 頻道
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636469446904_image.png
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636469390721_image.png
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504016837_image.png
建立Docker Image
FROM python:3-slim-bullseye
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD [ "python", "app.py"]
docker build . -t line-bot:latest
安裝Heroku CLI
https://devcenter.heroku.com/articles/heroku-cli#download-and-install
建立Heroku App
#登入heroku
heroku login
#登入heroku registry
heroku container:login
#建立app
heroku create
 
#Creating app... done, ⬢ morning-journey-43865
#https://morning-journey-43865.herokuapp.com/ | https://git.heroku.com/morning-journey-43865.git
 
#推送docker image至heroku registry
docker tag line-bot:latest registry.heroku.com/morning-journey-43865/web
docker push registry.heroku.com/morning-journey-43865/web
 
 
#設定環境變數
heroku config:set CHANNEL_ACCESS_TOKEN=xxxxxxxxx
heroku config:set CHANNEL_SECRET=xxxxxxxxx
 
#deploy docker image
heroku container:release web
設定Webhook
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636471948301_image.png
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636471973201_image.png
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504063885_image.png
https://devcenter.heroku.com/articles/container-registry-and-runtime
加入好友與測試
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504317204_image.png
s_02C5CCE7C60EF0F5CFCCC6A693EC6F4D19DF8DD8DD90E613893867B993C44DE5_1636504378568_image.png
Debug
heroku logs --tail
2021-11-09T15:17:06.932541+00:00 app[api]: Release v1 created by user macchiang@gmail.com
2021-11-09T15:17:06.932541+00:00 app[api]: Initial release by user macchiang@gmail.com
2021-11-09T15:17:07.532399+00:00 app[api]: Release v2 created by user macchiang@gmail.com
2021-11-09T15:17:07.532399+00:00 app[api]: Enable Logplex by user macchiang@gmail.com
2021-11-09T15:22:12.485507+00:00 app[api]: Deployed web (1be43e5135d6) by user macchiang@gmail.com
2021-11-09T15:22:12.485507+00:00 app[api]: Release v3 created by user macchiang@gmail.com
2021-11-09T15:22:12.503987+00:00 app[api]: Scaled to web@1:Free by user macchiang@gmail.com
2021-11-09T15:22:15.916499+00:00 heroku[web.1]: Starting process with command `python app.py`
2021-11-09T15:22:18.365293+00:00 app[web.1]: Traceback (most recent call last):
2021-11-09T15:22:18.365308+00:00 app[web.1]: File "/app/app.py", line 15, in <module>
2021-11-09T15:22:18.365410+00:00 app[web.1]: line_bot_api = LineBotApi(os.getenv('CHANNEL_ACCESS_TOKEN'))
2021-11-09T15:22:18.365411+00:00 app[web.1]: File "/usr/local/lib/python3.10/site-packages/linebot/api.py", line 63, in __init__
2021-11-09T15:22:18.365516+00:00 app[web.1]: 'Authorization': 'Bearer ' + channel_access_token,
2021-11-09T15:22:18.374292+00:00 app[web.1]: TypeError: can only concatenate str (not "NoneType") to str
2021-11-09T15:22:18.660220+00:00 heroku[web.1]: Process exited with status 1
2021-11-09T15:22:19.074969+00:00 heroku[web.1]: State changed from starting to crashed
2021-11-09T15:22:19.218365+00:00 heroku[web.1]: State changed from crashed to starting
2021-11-09T15:22:22.107500+00:00 heroku[web.1]: Starting process with command `python app.py`
2021-11-09T15:22:23.219784+00:00 app[web.1]: Traceback (most recent call last):
2021-11-09T15:22:23.219806+00:00 app[web.1]: File "/app/app.py", line 15, in <module>
2021-11-09T15:22:23.219930+00:00 app[web.1]: line_bot_api = LineBotApi(os.getenv('CHANNEL_ACCESS_TOKEN'))
2021-11-09T15:22:23.219934+00:00 app[web.1]: File "/usr/local/lib/python3.10/site-packages/linebot/api.py", line 63, in __init__
2021-11-09T15:22:23.219959+00:00 app[web.1]: 'Authorization': 'Bearer ' + channel_access_token,
2021-11-09T15:22:23.219961+00:00 app[web.1]: TypeError: can only concatenate str (not "NoneType") to str
2021-11-09T15:22:23.417290+00:00 heroku[web.1]: Process exited with status 1
2021-11-09T15:22:23.492672+00:00 heroku[web.1]: State changed from starting to crashed
2021-11-09T15:22:38.443725+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=morning-journey-43865.herokuapp.com request_id=b91dba06-950e-4887-840e-60fc5986866f fwd="36.227.157.168" dyno= connect= service= status=503 bytes= protocol=https
2021-11-09T15:22:39.424038+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=morning-journey-43865.herokuapp.com request_id=becc5a52-35a7-4557-b751-02d382a2b67e fwd="36.227.157.168" dyno= connect= service= status=503 bytes= protocol=https

american football Docker X PHP實作:架設一個 WordPress 網站

透過 Dockerfile 部署 PHP 環境

先從Php官方Dockerfile學起
https://github.com/macchiang/php-hello-world
Dockerfile
FROM php:7.3-apache
COPY index.php /var/www/html/
建立Docker image與Container
docker build . -t hello:latest
docker run -it --rm -p 8080:80 hello:latest
https://www.astralweb.com.tw/what-is-differences-between-fastcgi-php-fpm/

將 MySQL 加入部署行列

https://github.com/macchiang/php-docker-simple
docker-compose.yml
# Use root/example user/password credentials
version: '3.1'
 
services:
  php:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 80:80
    volumes:
      - ./src:/var/www/html/
 
  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: example
    volumes:
      - mysql-data:/var/lib/mysql
 
  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
 
volumes:
  mysql-data:

建立WordPress 網站

再由WordPresss官方Dockerfile學起
 #
# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh"
#
# PLEASE DO NOT EDIT IT DIRECTLY.
#
 
FROM php:7.3-apache
 
# persistent dependencies
RUN set -eux; \
        apt-get update; \
        apt-get install -y --no-install-recommends \
# Ghostscript is required for rendering PDF previews
                ghostscript \
        ; \
        rm -rf /var/lib/apt/lists/*
 
# install the PHP extensions we need (https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions)
RUN set -ex; \
        \
        savedAptMark="$(apt-mark showmanual)"; \
        \
        apt-get update; \
        apt-get install -y --no-install-recommends \
                libfreetype6-dev \
                libjpeg-dev \
                libmagickwand-dev \
                libpng-dev \
                libwebp-dev \
                libzip-dev \
        ; \
        \
        docker-php-ext-configure gd \
                --with-freetype-dir=/usr \
                --with-jpeg-dir=/usr \
                --with-png-dir=/usr \
                --with-webp-dir=/usr \
        ; \
        docker-php-ext-install -j "$(nproc)" \
                bcmath \
                exif \
                gd \
                mysqli \
                zip \
        ; \
# https://pecl.php.net/package/imagick
        pecl install imagick-3.5.0; \
        docker-php-ext-enable imagick; \
        rm -r /tmp/pear; \
        \
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
        apt-mark auto '.*' > /dev/null; \
        apt-mark manual $savedAptMark; \
        ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \
                | awk '/=>/ { print $3 }' \
                | sort -u \
                | xargs -r dpkg-query -S \
                | cut -d: -f1 \
                | sort -u \
                | xargs -rt apt-mark manual; \
        \
        apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
        rm -rf /var/lib/apt/lists/*
 
# see https://secure.php.net/manual/en/opcache.installation.php
RUN set -eux; \
        docker-php-ext-enable opcache; \
        { \
                echo 'opcache.memory_consumption=128'; \
                echo 'opcache.interned_strings_buffer=8'; \
                echo 'opcache.max_accelerated_files=4000'; \
                echo 'opcache.revalidate_freq=2'; \
                echo 'opcache.fast_shutdown=1'; \
        } > /usr/local/etc/php/conf.d/opcache-recommended.ini
# https://wordpress.org/support/article/editing-wp-config-php/#configure-error-logging
RUN { \
# https://www.php.net/manual/en/errorfunc.constants.php
# https://github.com/docker-library/wordpress/issues/420#issuecomment-517839670
                echo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \
                echo 'display_errors = Off'; \
                echo 'display_startup_errors = Off'; \
                echo 'log_errors = On'; \
                echo 'error_log = /dev/stderr'; \
                echo 'log_errors_max_len = 1024'; \
                echo 'ignore_repeated_errors = On'; \
                echo 'ignore_repeated_source = Off'; \
                echo 'html_errors = Off'; \
        } > /usr/local/etc/php/conf.d/error-logging.ini
 
RUN set -eux; \
        a2enmod rewrite expires; \
        \
# https://httpd.apache.org/docs/2.4/mod/mod_remoteip.html
        a2enmod remoteip; \
        { \
                echo 'RemoteIPHeader X-Forwarded-For'; \
# these IP ranges are reserved for "private" use and should thus *usually* be safe inside Docker
                echo 'RemoteIPTrustedProxy 10.0.0.0/8'; \
                echo 'RemoteIPTrustedProxy 172.16.0.0/12'; \
                echo 'RemoteIPTrustedProxy 192.168.0.0/16'; \
                echo 'RemoteIPTrustedProxy 169.254.0.0/16'; \
                echo 'RemoteIPTrustedProxy 127.0.0.0/8'; \
        } > /etc/apache2/conf-available/remoteip.conf; \
        a2enconf remoteip; \
# https://github.com/docker-library/wordpress/issues/383#issuecomment-507886512
# (replace all instances of "%h" with "%a" in LogFormat)
        find /etc/apache2 -type f -name '*.conf' -exec sed -ri 's/([[:space:]]*LogFormat[[:space:]]+"[^"]*)%h([^"]*")/\1%a\2/g' '{}' +
 
RUN set -eux; \
        version='5.8.2'; \
        sha1='c3b1b59553eafbf301c83b14c5eeae4cf1c86044'; \
        \
        curl -o wordpress.tar.gz -fL "https://wordpress.org/wordpress-$version.tar.gz"; \
        echo "$sha1 *wordpress.tar.gz" | sha1sum -c -; \
        \
# upstream tarballs include ./wordpress/ so this gives us /usr/src/wordpress
        tar -xzf wordpress.tar.gz -C /usr/src/; \
        rm wordpress.tar.gz; \
        \
# https://wordpress.org/support/article/htaccess/
        [ ! -e /usr/src/wordpress/.htaccess ]; \
        { \
                echo '# BEGIN WordPress'; \
                echo ''; \
                echo 'RewriteEngine On'; \
                echo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; \
                echo 'RewriteBase /'; \
                echo 'RewriteRule ^index\.php$ - [L]'; \
                echo 'RewriteCond %{REQUEST_FILENAME} !-f'; \
                echo 'RewriteCond %{REQUEST_FILENAME} !-d'; \
                echo 'RewriteRule . /index.php [L]'; \
                echo ''; \
                echo '# END WordPress'; \
        } > /usr/src/wordpress/.htaccess; \
        \
        chown -R www-data:www-data /usr/src/wordpress; \
# pre-create wp-content (and single-level children) for folks who want to bind-mount themes, etc so permissions are pre-created properly instead of root:root
# wp-content/cache: https://github.com/docker-library/wordpress/issues/534#issuecomment-705733507
        mkdir wp-content; \
        for dir in /usr/src/wordpress/wp-content/*/ cache; do \
                dir="$(basename "${dir%/}")"; \
                mkdir "wp-content/$dir"; \
        done; \
        chown -R www-data:www-data wp-content; \
        chmod -R 777 wp-content
 
VOLUME /var/www/html
 
COPY --chown=www-data:www-data wp-config-docker.php /usr/src/wordpress/
COPY docker-entrypoint.sh /usr/local/bin/
 
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]
https://github.com/macchiang/wordpress-demo
docker-compose.yaml
version: "3.3"
services:
  db:
    image: mysql
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
  wordpress:
    depends_on:
      - db
    image: wordpress
    ports:
      - "8000:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
volumes:
  db_data:
 

ping pong Docker X Node.js實作:打造前後端具備的 To-Do List

建立Express + Nginx組合

https://github.com/macchiang/express-nginx
Nginx config
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_tokens off;
    client_max_body_size 15M;
    if ($http_x_forwarded_proto = "http") {
        return 301 https://$host$request_uri;
    }
    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://node:3000;
        proxy_redirect off;
    }
}
docker-compose.yml
version: "3"
services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx:/etc/nginx/conf.d
    links:
      - node
    ports:
      - 80:80
  node:
    build:
      context: .
    image: express-nginx:latest
 

打造To-Do List

https://github.com/macchiang/express-mongoDB-TodoList
Ddockerfile
# Base image
FROM node:16-alpine
RUN apk upgrade --no-cache
# Create app directory
RUN mkdir -p /app
WORKDIR /app
# Install app dependencies
COPY package.json package-lock.json ./
RUN npm install
# copy app source
COPY . /app
# service port
EXPOSE 3000
CMD [ "node", "app.js"]
docker-compose.yml
version: "3"
services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx:/etc/nginx/conf.d
    links:
      - node
    ports:
      - 80:80
  node:
    build:
      context: .
    depends_on:
      - mongo
  mongo:
    image: mongo:5
    ports:
      - 27017:27017
    volumes:
      - mongo_data:/data/db
volumes:
    mongo_data:

open book 參考資料

推薦學習

https://dev.to/ankit01oss/7-github-projects-to-supercharge-your-docker-practices-2i80

【上課筆記】【udemy】 Docker容器技术从入门到精通

上課講義


原始碼

【Demo】Python Flask + Redis 練習

準備一個python文件 app.py

from flask import Flask
from redis import Redis
import os
import socket

app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)


@app.route('/')
def hello():
    redis.incr('hits')
    return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"

準備一個Dockerfile

FROM python:3.9.5-slim

RUN pip install flask redis && \
    groupadd -r flask && useradd -r -g flask flask && \
    mkdir /src && \
    chown -R flask:flask /src

USER flask

COPY app.py /src/app.py

WORKDIR /src

ENV FLASK_APP=app.py REDIS_HOST=redis

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]

準備一個Redis image

 

$ docker image pull redis
$ docker image build -t flask-demo .
$ docker image ls
REPOSITORY   TAG          IMAGE ID       CREATED              SIZE
flask-demo   latest       4778411a24c5   About a minute ago   126MB
python       3.9.5-slim   c71955050276   8 days ago           115MB
redis        latest       08502081bff6   2 weeks ago          105MB

建立一Bridage

$ docker network create -d bridge demo-network
8005f4348c44ffe3cdcbbda165beea2b0cb520179d3745b24e8f9e05a3e6456d
$ docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
2a464c0b8ec7   bridge         bridge    local
8005f4348c44   demo-network   bridge    local
80b63f711a37   host           host      local
fae746a75be1   none           null      local

創建container

創建一個叫 redis-server 的container,連到 demo-network上

$ docker container run -d --name redis-server --network demo-network redis
002800c265020310231d689e6fd35bc084a0fa015e8b0a3174aa2c5e29824c0e
$ docker container ls
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS      NAMES
002800c26502   redis     "docker-entrypoint.s…"   4 seconds ago   Up 3 seconds   6379/tcp   redis-server

創建 flask container,連到 demo-network上

$ docker container run -d --network demo-network \
--name flask-demo \
--env REDIS_HOST=redis-server \
-p 5000:5000 flask-demo

打開瀏覽器訪問 http://127.0.0.1:5000

應該能看到類似下面的內容,每次刷新頁面,計數加1

Hello Container World! I have been seen 36 times and my hostname is 925ecb8d111a.

合併步驟成一腳本

# prepare image
docker image pull redis
docker image build -t flask-demo .

# create network
docker network create -d bridge demo-network

# create container
docker container run -d --name redis-server --network demo-network redis
docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo

 

【Docker】【錯誤處理】無法停止服務

*使用systemctl stop docker 無法停止服務

[root@localhost docker]# systemctl stop docker 
Warning: Stopping docker.service, but it can still be activated by:
  docker.socket

*解決

#停止
[root@localhost docker]# systemctl stop docker.socket
[root@localhost docker]# systemctl stop docker.service

#啟動
[root@localhost docker]# systemctl daemon-reload
[root@localhost docker]# systemctl restart docker

**如果還沒辦法解決

# 删除 docker socket 文件
cd /lib/systemd/system/  &&  rm -rf docker.socket
# 查看 docker 状态,已dead
sudo systemctl status docker.service
# 重新创建 docker.socket
cd /lib/systemd/system/  
vim docker.socket
# 添加如下内容
[Unit]
Description=Docker Socket for the API
PartOf=docker.service

[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
#啟動
[root@localhost docker]# systemctl daemon-reload
[root@localhost docker]# systemctl restart docker

 

【Webmin】相關

 

 查詢被Deny 需要從Zone default -> Allow queries form ... -> 【 Listed..】-> 加入 any -> 右上角 【 Apply 】

image-1681888539338.png

image-1681888508758.png

image-1681888698503.png

Docker 各系統安裝

各系統安裝連結


 

Jenkins

Jenkins - Official Image | Docker Hub

jenkins/jenkins - Docker Image | Docker Hub

 

docker run -d	\
	-p 8080:8080 -p 50000:50000 \
	-v ~/jenkins/home:/var/jenkins_home \
    --name jenkins \
    jenkins/jenkins:lts

ps. jenkins 抓取一定要帶版號不然會報錯

jenkins Tags | Docker Hub

Using default tag: latest
Error response from daemon: manifest for jenkins:latest not found: manifest unknown: manifest unknown
docker pull jenkins/jenkins:lts

ps. 要安裝軟體需要使用root 登入

docker exec -u root -it jenkins sh
touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’: Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
sudo chown -R 1000:1000 {mount jenkins home path}

ps.使用nginx revers proxy 現設定問題,需要設定 proxy_set_header

Reverse proxy configuration (jenkins.io)

server {
    listen       80;
    server_name  tomcat.srou.com;

    location / {
         sendfile off;
        proxy_pass   http://192.168.100.4:10031/;
        proxy_redirect     default;
        proxy_http_version 1.1;

        # Required for Jenkins websocket agents
        proxy_set_header   Upgrade           $http_upgrade;

        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_max_temp_file_size 0;

        #this is the maximum upload size
        client_max_body_size       10m;
        client_body_buffer_size    128k;

        proxy_connect_timeout      90;
        proxy_send_timeout         90;
        proxy_read_timeout         90;
        proxy_buffering            off;
        proxy_request_buffering    off; # Required for HTTP CLI commands
        proxy_set_header Connection ""; # Clear for keepalive
      
        
    }

}

 

gitlab

docker run -d \
	--name gitlab \
	-p 8080:80 -p 443:443 -p 22:22 \
    --privileged \
    --restart always \
    -v ~/gitlab/config:/etc/gitlab \
    -v ~/gitlab/logs:/var/log/gitlab \
    -v ~/gitlab/data:/var/opt/gitlab  \
    gitlab/gitlab-ce
 
-d : 讓容器(Container)在背景執行
--name : 指定容器(Container)的名稱
-p : 指定主端(Host)對應客端(Quest)的連接埠
--privileged : 容器(Container)内的 root 拥有真正的 root 权限
-restart : 設定為 restart,則容器在異常停止後,會自動重新起動
-v : 將容器(Container)內需永續保存的目錄,連結至主端的目錄

 docker-compose.yml 

docker-compose up -d
version: "3.6"
services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    restart: always
    privileged: true
    ports:
      - '8080:80'
      - '443:443'
      - '22:22'
    volumes:
      - '~/gitlab/data:/var/opt/gitlab'
      - '~/gitlab/logs:/var/log/gitlab'
      - '~/gitlab/config:/etc/gitlab'

Tomcat

Docker部署tomcat出現404錯誤 - 台部落 (twblogs.net)

新版tomcat 不提供manager ,需手動複製,webapps.dist copy -> webapps


PostgresSQL

version: "3.7"
services:
    db:
        image: postgres:13.2-alpine
        restart: always
        environment:
            POSTGRES_DB: postgres
            POSTGRES_USER: $USER_NAME #postgres
            POSTGRES_PASSWORD: $PASSWORD #1234
            PGDATA: /var/lib/postgresql/data
        volumes:
            - db-data:/var/lib/postgresql/data
        ports:
            - "5432:5432"
    pgadmin:
        image: dpage/pgadmin4:latest
        restart: always
        environment:
            PGADMIN_DEFAULT_EMAIL: $USER_EMAIL #xxx@gmail
            PGADMIN_DEFAULT_PASSWORD: $PASSWORD #abcd
            PGADMIN_LISTEN_PORT: 80
        ports:
            - "8080:80"
        volumes:
            - pgadmin-data:/var/lib/pgadmin
        links:
            - "db:pgsql-server"
volumes:
    db-data:
    pgadmin-data:

 

Docker 安全性

 

為何在 Docker 中執行特權容器不是個好主意? – 資安趨勢部落格 (trendmicro.com.tw)

Docker 相關連結

docker 測試環境(play with docker)

Play with Docker (play-with-docker.com)


 

【上課講義】【udemy】 Docker容器技术从入门到精通
【上課講義】【hiskio】Docker 完全實作!結合 3 大語言掌握容器實務應用

Docker 完全實作 – Dropbox Paper

【鐵人賽】用30天來介紹和使用 Docker

用30天來介紹和使用 Docker :: 2018 iT 邦幫忙鐵人賽 (ithome.com.tw)

Docker File 官方說明

Dockerfile reference | Docker Documentation

Spring Boot with Docker

Getting Started | Spring Boot with Docker

Docker 從入門到實踐

前言 - 《Docker —— 從入門到實踐­》正體中文版 (gitbook.io)

Docker 與 JAVA開發實戰-程序员欣宸

https://github.com/zq2599/blog_demos


各系統安裝連結

 

 

Docker 部署IIS

Docker 監控相關

cadvisor 監控主機狀況

# 使用cadvisor
$ docker run -d --restart=always -v /var/run:/var/run:rw -p 8080:8080 --name=cadvisor google/cadvisor:latest

netdata 監控主機狀況

Install Netdata with Docker | Learn Netdata

docker run -d --name=netdata \
  -p 19999:19999 \
  -v netdataconfig:/etc/netdata \
  -v netdatalib:/var/lib/netdata \
  -v netdatacache:/var/cache/netdata \
  -v /etc/passwd:/host/etc/passwd:ro \
  -v /etc/group:/host/etc/group:ro \
  -v /proc:/host/proc:ro \
  -v /sys:/host/sys:ro \
  -v /etc/os-release:/host/etc/os-release:ro \
  --restart unless-stopped \
  --cap-add SYS_PTRACE \
  --security-opt apparmor=unconfined \
  netdata/netdata

Docker 儲存

image-1675737589104.png

--mount 與 -v/ --volume

當在使用 docker create 或是 docker run 的指令時,會有兩個選項可以使用,個別是 --mount 以及 -v (或是 --volume)。

注意,千萬不要誤以為使用 -v 就是用 Volume 的方式或者是 --mount 就是使用 Bind Mount 的方式,不是這樣的!!我們先來看看這兩個在 docker create  docker run 中的說明是甚麼:

-v, --volume list      Bind mount a volume
    --mount mount      Attach a filesystem mount to the container

但其實是可以用 --mount 或 -v 來綁定資料到 Volume,也可以用 --mount 或 -v 來綁定資料到 Filesystem。既然都可以,那為什麼要分兩種呢?

這是因為 --mount 其實出現的比較早,但他設定綁定資料的方式比較完整卻麻煩,因此在後續又出現了 -v 的選項來簡化綁定的設定過程。


Data Volume

相關指令

# 建立volume 
# docker volume create [OPTIONS] [VOLUME]
$docker volume create my-vol

#使用 -v
$ docker run -d --name my-http -v my-vol:/usr/local/apache2 -p 8080:80 httpd
#使用 --mount
$ docker run -d --name my-http --mount source=my-vol,target=/usr/local/apache2 -p 8080:80 httpd
# 列出所有volume
[tomcat@docker]$ docker volume ls
DRIVER              VOLUME NAME
local               2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
local     			my-vol

# 查詢某volume細節
[tomcat@docke]$ docker volume inspect 2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
[
    {
        "CreatedAt": "2019-06-04T15:40:43+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8/_data",
        "Name": "2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8",
        "Options": null,
        "Scope": "local"
    }
]

[tomcat@docke]$ docker volume inspect my-vol
[
    {
        "CreatedAt": "2023-02-07T02:11:18Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]


# 刪除所有不使用的volume
[tomcat@docker]$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
44d6286d1a793ac8c788b0fd364fa8d9430ce3ec079731dbe894dd996cae9adb
925559f4c2399c0c989d8602ca77fb3243dfb83022fe029b09b130c9521e7f89
9484acbd73c53af47f29add893c5125b0cd7f0f96e521c54e643f1aeb8f97eeb
b47202da45060de8f67e48058966b32078d525cadd1c94671a0ffe33f3d5df4b
006d620ae77bf7530ea37abd48b9c20ac264956f4b9ef3397e2c5072f32677c2
15e0fd2e575c2c24a2a137ff420dbf30326f3859d483561efc45e568d294c802
98b5385b04a401e9e870c5a1e807d69c56a1764b2ec91baedd9c13994cb2c863
ffdae0e84d00966afdc0df55d664d06ee99c14d249ad7558848088c464d472ff

Total reclaimed space: 77.8MB

# 指定volume mount 至 /var/www/html
[tomcat@docker]$ docker run -d -v html_doc:/var/www/html httpd



Bind Mount to FileSystem

# 直接mount 外部資料夾 至 /var/www/html
# 相對路徑
[tomcat@docker-qc html-doc]$ docker run -d -v ${PWD}/html-doc:/var/www/html httpd
# 絕對路徑
[tomcat@docker-qc html-doc]$ docker run -d -v /home/tomcat/docker/html-doc:/var/www/html httpd

Bind Mount to Memory

Tmpfs,全名 Temporary File System,是在 Linux 暫存檔儲存空間的常見名稱,通常以掛載檔案系統方式實現。他可以將資料儲存在記憶體中而不是存在硬碟內,所以只要一重新開機,資料則會被抹除。

Tmpfs 如其名,如果使用 Tmpfs Mount 則資料會存在於記憶體上,在重啟 Container 的時候就會 Reset 資料。

特別注意的是,使用 Tmpfs Mount 並不是使用 -v 的選項,而是使用 --tmpfs --tmpfs 就如同 -v一樣簡化了使用 --mount 時所需的設定。

使用 --tmpfs
$ docker run -d --name my-http --tmpfs /usr/local/apache2/htdocs -p 8080:80 httpd
使用 --mount
$ docker run -d --name my-http --mount type=tmpfs,destination=/usr/local/apache2/htdocs -p 8080:80 httpd

 


 

image-1642495777057.png

官方参考链接 https://docs.docker.com/storage/volumes/#share-data-among-machines


常用volume mapping (log)

# nginx
docker run -p 80:80 -itd -v /tmp/log/nginx/docker-nginx-log:/var/log/nginx nginx

使用綁定的不同時機比較

既然 Docker 提供了這麼多的方式讓我們儲存資料,那麼我們應該在甚麼情況下使用哪一種方式來儲存呢?

- 使用 Volume 的時機

- 使用 Bind Mount 的時機

- 使用 Tmpfs Mount 的時機

Docker筆記 - 讓資料遠離Container,使用 Volume、Bind Mount 與 Tmpfs Mount

 

Docker Compose

安裝

Install Docker Compose | Docker Documentation

# linux
# 到這裡找版號 https://github.com/docker/compose/releases
# sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
$ sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c

# 找不到命令的可以建立軟連結
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

熟悉python的朋友,可以使用pip去安装docker-Compose

$ pip install docker-compose

YML 語法結構

compose-spec/spec.md at master · compose-spec/compose-spec · GitHub

version: "3.8"

services: # 容器
  servicename: # 服務名字,這個名字也是內部 bridge網絡可以使用的 DNS name
  	container_name: #自定義container 名稱,不使用會用 servicename
    # build: ./buildfolder
    build: # 加此參數會使用docker file 建立
    # 如果要指定 dockerfile 使用 context + dockerfile
    #build:
    #  context: ./buildfolder
    #  dockerfile: Dockerfile.dev
    image: # 鏡像的名字(如果使用build,此選項會變成image名稱,如果沒有build,會從網路拉取該名稱image)
    command: # 可選,如果設置,則會覆蓋默認鏡像裡的 CMD命令, ex: /bin/sh -c “while true;do sleep 3600 ;done”
    environment: # 可選,相當於 docker run裡的 --env
    volumes: # 可選,相當於docker run裡的 -v
    networks: # 可選,相當於 docker run裡的 --network
    ports: # 可選,相當於 docker run裡的 -p
    
  servicename2: #第二個服務

volumes: # 可選,相當於 docker volume create

networks: # 可選,相當於 docker network create

範例

version: "3.8"

services:
  flask-demo:
    image: flask-demo:latest
    environment:
      - REDIS_HOST=redis-server
      - TZ=Asia/Taipei #設定時區
    networks:
      - demo-network
    ports:
      - 8080:5000
    volumes:
      - ./config/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro,Z 
      - ./config/logstash/pipeline:/usr/share/logstash/pipeline:ro,Z #ro: read only, Z: 在SElinux 下唯讀(啟用SEliunx才有用)

  redis-server:
    image: redis:latest
    networks:
     - demo-network

networks:
  demo-network:
docker-compose 與 docker 版本對應,語法說明
https://docs.docker.com/compose/compose-file/
docker run 轉換 docker-compose 語法工具

https://www.composerize.com/

常用指令

# 前景執行
docker-compose up
# 背景執行
docker-compose up -d
# 使用自定義前贅字(預設是用資料夾名稱)
docker-compose -p my_project up -d

# 停止docker-compose
docker-compose stop

# 移除docker-compose
docker-compose down
# 移除docker-compose 同時移除volumn
docker-comopose down -v

# 列出container
docker-compose ps 

# 列出log
docker-compose log
docker-compose log -f

# 在webapp 內執行sh
docker-compose exec webapp sh

# 使用top監控
docker-compose top

# 抓取(build)image
docker-compose pull

更新服務

當程式有異動,想要重新build image

# 重新build image
docker-compose build
# 啟動時重新build
docker-compose up --build
# 如果有服務被移除,使用 --remove-orphans 移除相關資源
docker-composer up --remove-orphans
# 如果有變更掛載,需要重啟服務
docker-composer restart

網路配置

networks:
  mynetwork1:
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16
   mynetwork2:
     ipam:
      driver: default
      config:
        - subnet: 172.29.0.0/16

 

清除環境

docker-compose stop
docker-compose rm 
docker system prund -f

 

image-1642476741214.png

 

docker-compose volume 寫法

image-1642476831997.png

 

 

image-1642476893794.png

docker compose yml 範例

version: "3"
services:
  webapp:
    #指定image
    image: httpd:alpine
    ports:
        - "80:80"
    volumes:
        - "/data"

 

version: "3"
services:
  webapp:
    #指定image
    build:
	context: .
        dockerfile: Dockerfile-alternate
	args:
		buildno:  1

 相關連結

 

 

Docker container 常用軟體安裝

更新 apt-get

#更新apt-update
apt-get update

vim

sudo apt-get install -y vim

ip

sudo apt-get install -y iproute2

ping

sudo apt-get install -y iputils-ping

ifconfig, netestat

sudo apt-get install -y net-tools
nc (netcat)
sudo apt-get install -y netcat

maven

FROM jenkins/jenkins:lts
RUN apt-get update && apt-get install -y maven

apt-get 使用proxy


#apt_proxy.conf 設定 proxy
Acquire::http::proxy "http://127.0.0.1:8000/";
Acquire::ftp::proxy "ftp://127.0.0.1:8000/";
Acquire::https::proxy "https://127.0.0.1:8000/";

#可以用“-c”選項來指定使用配置檔案
sudo apt-get -c ~/apt_proxy.conf update
#apt-get也有一個“-o”選項,直接跟apt-get的設定變數,就不用指定配置檔案了,比如
sudo apt-get -o Acquire::http::proxy="http://127.0.0.1:8000/" update

Docker File

Docker File 官方說明

Dockerfile reference | Docker Documentation

官方各類型docker file 專案連結(可找到專案git repo)

official-images/library at master · docker-library/official-images · GitHub

image-1645294864450.png


Build Dockerfile

# docker image build -t {name}:{tag} {dokerfile 目錄}
docker image build -t hello_py:v1 .

Docker File 結構

# 基礎鏡像檔
FROM ubuntu:21.04
# 環境變數
ENV VERSION=2.0.1
# 執行命令(可在執行容器的時候使用其他命覆蓋)
RUN apt-get update && \
    apt-get install -y wget && \
    wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
    tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
    mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
    rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
# 移動到當前目錄
WORKDIR /app
# 複製檔案
COPY hello.py /app/hello.py
# 複製檔案(如果是壓縮檔會自行解壓縮,一般來說還是用COPY)
ADD hello.tar.gz /app/
# 執行命令(無法覆蓋)
ENTRYPOINT ["echo", "hello docker"]

Docker Image 相關

鏡像的獲取

官方image:
https://hub.docker.com/search?type=image&image_filter=official


常用 public image server

 

*建立映像檔
# 於當前目錄,按Dockerfile.test中的指令,建立test:v1 ({name:tag})的映像檔
docker build . -f Dockrfile.test -t test:v1
*拉取與推送映像檔
# 使用docker hub 下載image ({name:tag})
docker pull node:alpine
# 使用quay.io 下載image 
docker pull quay.io/bitnami/tomcat
# node:alpine 重新命名標籤 username/nodea:alpine
docker tag node:alpine username/nodea:alpine
# 推送至私人倉庫
docker push username/node:alpine

使用私有registry
# 修改鏡像registry
sudo vim /etc/docker/daemon.json
#########################################################
#  可與許http鏡像 "insecure-registries" : ["10.60.78.79"],
#  鏡像站 "registry-mirrors": ["http://hub-mirror.c.163.com"]

{
  "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
#########################################################
# 使文件生效
sudo systemctl daemon-reload
# 重啟docker
sudo service docker restart
使用proxy 抓取鏡像檔

*ubuntu

# 編輯設定檔
vim /etc/default/docker

##檔案內容
export http_proxy="http://192.168.33.10:3128/"
export https_proxy="http://192.168.33.10:3128/"
##

# proxy 如果有帳密驗證
export http_proxy="https://username:password@192.168.33.10:3128/"

# 重啟docker 
systemctl restart docker

*centos

vim /etc/systemd/system/docker.service.d/http-proxy.conf 

###################################################################################
[Service]
Environment="HTTP_PROXY=http://192.168.1.1:3128/"
Environment="HTTPS_PROXY=http://192.168.1.1:3128/"
#如果要排除不走
Environment="NO_PROXY=localhost,127.0.0.0/8,docker-registry.somecorporation.com"

###################################################################################

#重讀設定
systemctl daemon-reload

#重啟docker
systemctl restart docker

查看image
# 取得id
[root@localhost ~]# docker image ls

REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
tomcat       latest         9f35737a8466   30 hours ago   680MB
busybox      latest         beae173ccac6   3 weeks ago    1.24MB
openjdk      8-jre-alpine   f7a292bbb70c   2 years ago    84.9MB
[root@localhost ~]# docker image inspect f7a292bbb70c
[
    {
        "Id": "sha256:f7a292bbb70c4ce57f7704cc03eb09e299de9da19013b084f138154421918cb4",
        "RepoTags": [
            "openjdk:8-jre-alpine"
        ],
        "RepoDigests": [
            "openjdk@sha256:f362b165b870ef129cbe730
.......

# 重要訊息
 "Id": "sha256:f7a292bbb70c4ce57f7704cc03eb09e299de9da19013b084f138154421918cb4",
        "RepoTags": [
            "openjdk:8-jre-alpine"
        ],


"Architecture": "amd64",
 "Os": "linux",
刪除 image
# 查詢要刪除imge id
[root@localhost ~]# docker image ls
REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
tomcat       latest         9f35737a8466   30 hours ago   680MB
busybox      latest         beae173ccac6   3 weeks ago    1.24MB
openjdk      8-jre-alpine   f7a292bbb70c   2 years ago    84.9MB
# 刪除 image
[root@localhost ~]# docker image rm beae173ccac6
Untagged: busybox:latest
Untagged: busybox@sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678
Deleted: sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a
Deleted: sha256:01fd6df81c8ec7dd24bbbd72342671f41813f992999a3471b9d9cbc44ad88374

# 刪除名字跟nginx 有關的imge (-f 強制)
[root@localhost ~]# docker images | grep nginx | awk '{print $3}' | xargs docker rmi
[root@localhost ~]# docker images | grep nginx | awk '{print $3}' | xargs docker rmi -f

# 刪除沒有名字的image (-f 強制)
[root@localhost ~]# docker images | grep "<none>" | awk '{print $3}' | xargs docker rmi
[root@localhost ~]# docker images | grep "<none>" | awk '{print $3}' | xargs docker rmi -f
image 匯入匯出
# 查看 image
[root@localhost ~]# docker image ls
REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
tomcat       latest         9f35737a8466   30 hours ago   680MB
busybox      latest         beae173ccac6   3 weeks ago    1.24MB
openjdk      8-jre-alpine   f7a292bbb70c   2 years ago    84.9MB

# 匯出 image
# docker image save {name}:{tag} -o {匯出檔名}
[root@localhost ~]# docker image save openjdk:8-jre-alpine -o openjdk:8-jre-alpine.image
# 查看是否匯出
[root@localhost ~]# ls openjdk\:8-jre-alpine.image 
openjdk:8-jre-alpine.image
# image 匯入
# docker image load -i {image file}
[root@localhost ~]# docker image load -i ./openjdk\:8-jre-alpine.image 
f1b5933fe4b5: Loading layer [==================================================>]  5.796MB/5.796MB
9b9b7f3d56a0: Loading layer [==================================================>]  3.584kB/3.584kB
edd61588d126: Loading layer [==================================================>]  80.28MB/80.28MB
Loaded image: openjdk:8-jre-alpine

push image to docker hub

# 建立上傳image
#docker image tag nginx {docker hub id}/{name}:{tag}
docker image tag nginx treemanou/my_nginx:v2
#docker image push {docker hub id}/{name}:{tag}
docker image push treemanou/my_nginx:v2

commit 建立 image

# 找一個conatiner
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS                      PORTS     NAMES
e09bb406dcef   busybox   "sh"      53 seconds ago   Exited (0) 18 seconds ago             hardcore_bartik
# 利用此container建立 image
# docker commit {container id} {name}:{tag}
$ docker commit e09 treemanou/my_busybox:v3
sha256:42bc77669110af56336ec10afdbd7f92fa371f02ecccbda1e0bbb1bbc00f3cf6

Docker File


Docker File 官方說明

Dockerfile reference | Docker Documentation


*ENV 環境變數

ENV ANT_HOME /opt/ant

*ARG傳入參數

FROM alpine
ARG NODE_ARG
ENV NODE_ENV="${NODE_ARG:-development}"
RUN echo "ARG=${NODE_ARG}, ENV=${NODE_ENV}"
CMD ehco "ARG=${NODE_ARG}, ENV=${NODE_ENV}"
# 傳入NODE_ARG
# DOCKER_BUILDKIT=0
DOCKER_BUILDKIT=0 docker build --build-arg NODE_ARG=staging .

*工作目錄 WORKDIR

# /  下
RUN echo "say hi" > hi.txt
WORKDIR /app
#  /app 下
RUN echo "hello" > world.txt

*指定用戶USER (不指定舊識)

# 新增 group與user
RUN groupadd -r redis && useradd -r -g redis redis
# 指定當前user為redis
USER redis

# 以redis的身份執行reidis-server
RUN ["redis-server"]

*Health Check

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1

 

FROM registry.test.com.tw/test/centos:openjdk8_tomcat8.5

ENV CATALINA_HOME /opt/tomcat
ENV SERVICE_HOME /usr/AP/fubon
ENV ANT_HOME /opt/ant
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk
ENV PATH="${PATH}:${ANT_HOME}/bin:${CATALINA_HOME}/bin"
ENV AWS_HOME="/root/aws"

RUN wget https://archive.apache.org/dist/ant/binaries/apache-ant-1.10.7-bin.tar.gz \
  && tar -zxvf apache-ant-1.10.7-bin.tar.gz \
  && rm apache-ant-1.10.7-bin.tar.gz \
  && mv apache-ant* ${ANT_HOME} \
  && yum install -y python3 \
  && pip3 install awscli --upgrade --user \
  && ln -s  /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/ /usr/lib/jvm/java-1.8.0-openjdk.x86_64 \
  && yum install -y httpd php php-gd 
  
COPY server.xml ${CATALINA_HOME}/conf/

COPY my_entrypoint.sh /my_entrypoint.sh
RUN chmod -v +x /my_entrypoint.sh

COPY --chown=user:group file* /app/

COPY server_builder.sh ${SERVICE_HOME}/

#Set Alias
RUN echo "alias cdcm='cd /usr/AP/test/cm'" >> ~/.bashrc \
 && echo "alias restartTomcat='cd /opt/tomcat/bin; ./shutdown.sh; /bin/sleep 3; ps -ef | grep '\''org.apache.catalina.startup.Bootstrap start'\'' | grep -v grep | awk '\''{print $2}'\'' | xargs kill; ./startup.sh'" >> ~/.bashrc \
 && echo "alias cmant='cd /usr/AP/test/cm/src; ant; restartTomcat'"  >> ~/.bashrc \
 
EXPOSE 8080

CMD ["-D","FOREGROUND"]

# 預設目錄
WORKDIR /usr/AP/test/ 

#  一定會執行
ENTRYPOINT ["/my_entrypoint.sh"]

* 複寫CMD

docker run --rm apache:v1 -v

多階段建置

# 建置環境
FROM alpine as build
RUN echo "hello" > mytest

#  執行環境
FROM alpine
COPY --from=build /mytest .
RUN cat /mytest 

Distroless Dock image  => 精簡image


常用image

busybox

containous/whoami
vagrant@swarm-manager:~$ curl 192.168.200.10:8080
Hostname: fdf7c1354507
IP: 127.0.0.1
IP: 10.0.0.7
IP: 172.18.0.3
IP: 10.0.1.14
RemoteAddr: 10.0.0.2:36828
GET / HTTP/1.1
Host: 192.168.200.10:8080
User-Agent: curl/7.68.0
Accept: */*

Docker Image Server DockerRegistry

 

Day7:建立 private 的 Docker Registry - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天 (ithome.com.tw)

$ docker run -d -p 5000:5000 -v /home/user1/storage:/var/lib/registry --name registry registry:2

#
# -d:執行的 docker container 是 run 在背景的狀態,所以需要使用 docker logs 的指令才可以看到 log 狀態
# -p:主機的 5000 port mapping 到 container 的 5000 port
# -v:因為 push 到 docker registry 的資料是放在 container 裡面的,如果把 docker container 刪除掉 docker registry 的 Image 資料就會不見,因此需要使用 –v 參數將主機的檔案路徑 mapping 到 container 裡面的檔案路徑,這樣 docker container 被刪除 docker registry 的 Image 資料還會存在
# --name:設定 docker container 的名稱
clinet / server 都要加 daemon.json 
# vim /etc/docker daemon.json
# 允許192.168.1.1:5000 (port 一定要寫) 可不走https
{
"insecure-registries":["192.168.1.1:5000"]
}

 

上傳 image

# 假設本機ip 192.168.1.1 (registry server)
# 下載image
docker pull busybox

# 複製image 成 mybusybox
docker push tag busybox 192.168.1.1:5000/mybusybox
# 上傳image 
docker pull 192.168.1.1:5000/mybusybox

下載 image

# 假設本機ip 192.168.1.2 (docker client)
docker pull docker pull 192.168.1.1:5000/mybusybox

Docker Image Server nexus

 

sonatype/nexus3 - Docker Image | Docker Hub

  1. Use a docker volume. Since docker volumes are persistent, a volume can be created specifically for this purpose. This is the recommended approach.

    $ docker volume create --name nexus-data
    $ docker run -d -p 8081:8081 --name nexus -v nexus-data:/nexus-data sonatype/nexus3
  2. Mount a host directory as the volume. This is not portable, as it relies on the directory existing with correct permissions on the host. However it can be useful in certain situations where this volume needs to be assigned to certain specific underlying storage.

    $ mkdir /some/dir/nexus-data && chown -R 200 /some/dir/nexus-data
    $ docker run -d -p 8081:8081 --name nexus -v /some/dir/nexus-data:/nexus-data sonatype/nexus3

Docker Network

image-1642500508897.png

image-1642576871696.png

建立兩容器

$ docker container run -d --rm --name box1 busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container run -d --rm --name box2 busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container ls
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
4f3303c84e53   busybox   "/bin/sh -c 'while t…"   49 minutes ago   Up 49 minutes             box2
03494b034694   busybox   "/bin/sh -c 'while t…"   49 minutes ago   Up 49 minutes             box1

容器間的通信->使用bridge

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
1847e179a316   bridge    bridge    local
a647a4ad0b4f   host      host      local
fbd81b56c009   none      null      local
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "1847e179a316ee5219c951c2c21cf2c787d431d1ffb3ef621b8f0d1edd197b24",
        "Created": "2021-07-01T15:28:09.265408946Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "03494b034694982fa085cc4052b6c7b8b9c046f9d5f85f30e3a9e716fad20741": {
                "Name": "box1",
                "EndpointID": "072160448becebb7c9c333dce9bbdf7601a92b1d3e7a5820b8b35976cf4fd6ff",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            },
            "4f3303c84e5391ea37db664fd08683b01decdadae636aaa1bfd7bb9669cbd8de": {
                "Name": "box2",
                "EndpointID": "4cf0f635d4273066acd3075ec775e6fa405034f94b88c1bcacdaae847612f2c5",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

"IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
    {
         "Subnet": "172.17.0.0/16",
        "Gateway": "172.17.0.1"
    }
    ]
},

"com.docker.network.bridge.name": "docker0",

# brctl 顯示bridge tool
$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242759468cf       no              veth8c9bb82
                                                        vethd8f9afb

容器對外通訊->iptables nat

$ ip route
default via 10.0.2.2 dev eth0 proto dhcp metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.200.0/24 dev eth1 proto kernel scope link src 192.168.200.10 metric 101
$ sudo iptables --list -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere            !loopback/8           ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        anywhere

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere

image-1642576581794.png

docker network create --help

docker network create -d {driver} --gateway {gateway} --subnet {subnet} {name}
docker network create -d bridge --gateway 127.200.0.1 -subnet 172.200.0.0/16 demo

[tomcat@docker-qc html-doc]$ docker network create -d bridge mybridge
65c5ba6cbea2fa677e8dd0623a510f3461e24ccac196b7374fc5e8d03803ec9e

[tomcat@docker-qc html-doc]$  docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
780162e5fe57        bridge              bridge              local
3c5b59c8a708        host                host                local
65c5ba6cbea2        mybridge            bridge              local

[tomcat@docker-qc html-doc]$ docker network inspect mybridge
[
    {
        "Name": "mybridge",
        "Id": "65c5ba6cbea2fa677e8dd0623a510f3461e24ccac196b7374fc5e8d03803ec9e",
        "Created": "2022-01-18T18:44:39.95740673+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]
# 建立連接mybridge的container (--network 不設定會用預設的)
$ docker container run -d --rm --name box3 --network mybridge busybox /bin/sh -c "while true; do sleep 3600; done"
# 既有container 連接mybridge
$ docker network connect mybridge box1
# container 中斷 mybridge
$ docker network disconnect mybridge box1

Network type

bridge: 新增一網段,橋接本地網路
host: 直接使用本地網路(無法看到port -> 等於直接開在本機port 80,好處不用nat,壞處只能開一個)

#docker dontainer run -d --name {container name} --network {network type} {image}
docker dontainer run -d --name web --network host nginx

image-1642651291388.png

只能開一個,多開會報錯

image-1642651575691.png

none: 只有 lookback 127.0.0.1 沒有其他網路


網路命名空間

#Ubuntu
apt-get install bridge-utils 
#Centos
sudo yum install bridge-utils

image-1642659997529.png

創建bridge

[vagrant@docker-host1 ~]$ sudo brctl addbr mydocker0
[vagrant@docker-host1 ~]$ brctl show
bridge name     bridge id               STP enabled     interfaces
mydocker0               8000.000000000000       no
[vagrant@docker-host1 ~]$

add-ns-to-br.sh

#!/bin/bash

bridge=$1
namespace=$2
addr=$3

vethA=veth-$namespace
vethB=eth00

sudo ip netns add $namespace
sudo ip link add $vethA type veth peer name $vethB

sudo ip link set $vethB netns $namespace
sudo ip netns exec $namespace ip addr add $addr dev $vethB
sudo ip netns exec $namespace ip link set $vethB up

sudo ip link set $vethA up

sudo brctl addif $bridge $vethA
#run shell
[vagrant@docker-host1 ~]$ sh add-ns-to-br.sh mydocker0 ns1 172.16.1.1/16
[vagrant@docker-host1 ~]$ sh add-ns-to-br.sh mydocker0 ns2 172.16.1.2/16

#up bridge
[vagrant@docker-host1 ~]$ sudo ip link set dev mydocker0 up

驗證

[vagrant@docker-host1 ~]$ sudo ip netns exec ns1 bash
[root@docker-host1 vagrant]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: eth00@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether f2:59:19:34:73:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.16.1.1/16 scope global eth00
    valid_lft forever preferred_lft forever
    inet6 fe80::f059:19ff:fe34:7370/64 scope link
    valid_lft forever preferred_lft forever
[root@docker-host1 vagrant]# ping 172.16.1.2
PING 172.16.1.2 (172.16.1.2) 56(84) bytes of data.
64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=0.029 ms
64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=0.080 ms
^C
--- 172.16.1.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.029/0.054/0.080/0.026 ms
[root@docker-host1 vagrant]#

image-1642660373795.png

image-1642660418946.png

使用iptables 實現nat
NAT with Linux and iptables - Tutorial (Introduction) (karlrupp.net)


使用自定義網路練習

使用自定義網路,預設自帶dns,容器間可相互使用name ping 通

準備python 檔案 app.py

from flask import Flask
from redis import Redis
import os
import socket

app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)


@app.route('/')
def hello():
    redis.incr('hits')
    return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"

準備一個 Dockerfile

FROM python:3.9.5-slim

RUN pip install flask redis && \
    groupadd -r flask && useradd -r -g flask flask && \
    mkdir /src && \
    chown -R flask:flask /src

USER flask

COPY app.py /src/app.py

WORKDIR /src

ENV FLASK_APP=app.py REDIS_HOST=redis

EXPOSE 5000

CMD ["flask", "run", "-h", "0.0.0.0"]

build image 並下載一個redis image

$ docker image pull redis
$ docker image build -t flask-demo .
$ docker image ls
REPOSITORY   TAG          IMAGE ID       CREATED              SIZE
flask-demo   latest       4778411a24c5   About a minute ago   126MB
python       3.9.5-slim   c71955050276   8 days ago           115MB
redis        latest       08502081bff6   2 weeks ago          105MB

建立一個bridge 

$ docker network create -d bridge demo-network
8005f4348c44ffe3cdcbbda165beea2b0cb520179d3745b24e8f9e05a3e6456d
$ docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
2a464c0b8ec7   bridge         bridge    local
8005f4348c44   demo-network   bridge    local
80b63f711a37   host           host      local
fae746a75be1   none           null      local

建立redis container 並連至 demo-network

$ docker container run -d --name redis-server --network demo-network redis
002800c265020310231d689e6fd35bc084a0fa015e8b0a3174aa2c5e29824c0e
$ docker container ls
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS      NAMES
002800c26502   redis     "docker-entrypoint.s…"   4 seconds ago   Up 3 seconds   6379/tcp   redis-server

建立 flask container 

$ docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo

打開瀏覽器 http://127.0.0.1:5000

可以看到以下內容,每次刷新次次就會+1
Hello Container World! I have been seen 36 times and my hostname is 925ecb8d111a.

整理後腳本
# prepare image
docker image pull redis
docker image build -t flask-demo .

# create network
docker network create -d bridge demo-network

# create container
docker container run -d --name redis-server --network demo-network redis
docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo

Docker Swarm

相關指令

# 查詢swarm狀態
docker info
# 未開啟
Swarm: inactive

# 初始化swarm
docker swarm init
##########################
Swarm initialized: current node (4szs3vf8x9pb79me4cxfsb9m2) is now a manager.

To add a worker to this swarm, run the following command:
	#加入叢集使用以下命令
    docker swarm join --token SWMTKN-1-3i2aq39bqqudp6p6y3o4ubw4ca1036z85z3huf9lyfijdfns88-33y43c8gw2ch85wbl2jdb4w0e 192.168.65.6:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
##########################

#查詢節點
docker node ls
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
4szs3vf8x9pb79me4cxfsb9m2 *   docker-desktop   Ready     Active         Leader           20.10.6

# 開啟後查詢 docker info
Swarm: active
  NodeID: 4szs3vf8x9pb79me4cxfsb9m2
  Is Manager: true
  ClusterID: 83phccx66ca4rji17wk8p5kce
  Managers: 1
  Nodes: 1
  Default Address Pool: 10.0.0.0/8
  SubnetSize: 24
  Data Path Port: 4789
  Orchestration:
  
 # 離開docker swarm 叢集
 docker swrm leave -f

 

docker swarm init 背後發生了什麼

主要是PKI和安全相關的自動化

創建Raft資料庫用於存儲證書,配置,密碼等數據


建立一個service(container)

# docker service create {image}:{tag}
docker service create --name web nginx:latest
# 取得servie id(不是container id)
docker service ls

ID             NAME           MODE         REPLICAS   IMAGE          PORTS
l5wvuxzw9l96   jolly_euclid   replicated   1/1        nginx:latest

# 查看container
docker service ps l5wvuxzw9l96
ID             NAME             IMAGE          NODE             DESIRED STATE   CURRENT STATE           ERROR     PORTS
vp8dkrsqslxw   jolly_euclid.1   nginx:latest   docker-desktop   Running         Running 3 minutes ago

# 建立3個副本(update)
docker service update l5wvuxzw9l96 --replicas 3
l5wvuxzw9l96
overall progress: 3 out of 3 tasks
1/3: running   [==================================================>]
2/3: running   [==================================================>]
3/3: running   [==================================================>]

# 建立4個副本(scale)
docker service scale l5wvuxzw9l96=4
web scaled to 4
overall progress: 4 out of 4 tasks 
1/4: running   [==================================================>] 
2/4: running   [==================================================>] 
3/4: running   [==================================================>] 
4/4: running   [==================================================>] 

#docker sercie ps
docker service ps l5wvuxzw9l96
ID             NAME             IMAGE          NODE             DESIRED STATE   CURRENT STATE            ERROR     PORTS
vp8dkrsqslxw   jolly_euclid.1   nginx:latest   docker-desktop   Running         Running 13 minutes ago
qavp0npxko3l   jolly_euclid.2   nginx:latest   docker-desktop   Running         Running 7 minutes ago
kqs6uvbkxmjh   jolly_euclid.3   nginx:latest   docker-desktop   Running         Running 7 minutes ago

service 刪除

 docker service rm l5wvuxzw9l96

docker swarm的網路:

image-1644943091590.png

 

Docker如何固定IP設定的方法

 

Docker如何固定IP設定的方法 - IT閱讀 (itread01.com)

經常用Docker模擬專案在生產環境中的部署,往往需要同時開好幾臺Docker容器,而且有時安裝的軟體需要繫結Docker區域網中的其他容器,如 MongoDB 副本集部署的時候,就需要繫結其他容器的內網IP。

但是,Docker 每次重啟後,容器的IP地址會變化,查詢了資料,Docker是支援設定固定IP的。

Docker 預設網路

Docker安裝後,預設會建立下面三種網路型別:

$ docker network ls
NETWORK ID     NAME        DRIVER       SCOPE
9781b1f585ae    bridge       bridge       local
1252da701e55    host        host        local
237ea3d5cfbf    none        null        local

啟動 Docker的時候,用 --network 引數,可以指定網路型別,如:

~ docker run -itd --name test1 --network bridge --ip 172.17.0.10 centos:latest /bin/bash

bridge:橋接網路

預設情況下啟動的Docker容器,都是使用 bridge,Docker安裝時建立的橋接網路,每次Docker容器重啟時,會按照順序獲取對應的IP地址,這個就導致重啟下,Docker的IP地址就變了

none:無指定網路

使用 --network=none ,docker 容器就不會分配區域網的IP

host: 主機網路

使用 --network=host,此時,Docker 容器的網路會附屬在主機上,兩者是互通的。

例如,在容器中執行一個Web服務,監聽8080埠,則主機的8080埠就會自動對映到容器中。

建立自定義網路:(設定固定IP)

啟動Docker容器的時候,使用預設的網路是不支援指派固定IP的,如下:

~ docker run -itd --net bridge --ip 172.17.0.10 centos:latest /bin/bash
6eb1f228cf308d1c60db30093c126acbfd0cb21d76cb448c678bab0f1a7c0df6
docker: Error response from daemon: User specified IP address is supported on user defined networks only.

因此,需要建立自定義網路,下面是具體的步驟:

步驟1: 建立自定義網路

建立自定義網路,並且指定網段:172.18.0.0/16

 ~ docker network create --subnet=172.18.0.0/16 mynetwork
 ~ docker network ls
NETWORK ID     NAME        DRIVER       SCOPE
9781b1f585ae    bridge       bridge       local
1252da701e55    host        host        local
4f11ae9c85de    mynetwork      bridge       local
237ea3d5cfbf    none        null        local

步驟2: 建立Docker容器

程式碼如下:

➜  ~ docker run -itd --name networkTest1 --net mynetwork --ip 172.18.0.2 centos:latest /bin/bash

這個時候,建立的Docker容器就會持有 172.18.0.2 這個IP.

[root@ec8e31938fe7 /]# ifconfig
eth0   Link encap:Ethernet HWaddr 02:42:AC:12:00:02
     inet addr:172.18.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
     inet6 addr: fe80::42:acff:fe12:2/64 Scope:Link
     UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
     RX packets:88 errors:0 dropped:0 overruns:0 frame:0
     TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:0
     RX bytes:4056 (3.9 KiB) TX bytes:1068 (1.0 KiB)

lo    Link encap:Local Loopback
     inet addr:127.0.0.1 Mask:255.0.0.0
     inet6 addr: ::1/128 Scope:Host
     UP LOOPBACK RUNNING MTU:65536 Metric:1
     RX packets:0 errors:0 dropped:0 overruns:0 frame:0
     TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
     collisions:0 txqueuelen:1
     RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)

Docker安裝

Centos

Docker Engine on CentOS | Docker Documentation

 sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
 sudo yum install -y yum-utils
 sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
 sudo yum-config-manager --enable docker-ce-nightly
 
 sudo yum install docker-ce docker-ce-cli containerd.io
 
 sudo systemctl start docker
 sudo systemctl enable docker

~/.docker/config.json

{
 "proxies":
 {
   "default":
   {
     "httpProxy": "http://192.168.1.12:3128",
     "httpsProxy": "http://192.168.1.12:3128",
     "noProxy": "*.test.example.com,.example2.com,127.0.0.0/8"
   }
 }
}

/etc/docker/daemon.json

{
"registry-mirrors":["http://hub-mirror.c.163.com"],
}
registry-1.docker.io.	54	IN	A	34.197.211.151
registry-1.docker.io.	54	IN	A	34.192.145.113
registry-1.docker.io.	54	IN	A	18.214.230.110
registry-1.docker.io.	54	IN	A	52.55.168.20
registry-1.docker.io.	54	IN	A	3.229.227.53
registry-1.docker.io.	54	IN	A	3.209.182.229
registry-1.docker.io.	54	IN	A	3.213.204.48
registry-1.docker.io.	54	IN	A	54.161.109.204
mkdir /etc/systemd/system/docker.service.d
vim /etc/systemd/system/docker.service.d/http-proxy.conf
########################################################
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:80/"
Environment="NO_PROXY=localhost,127.0.0.0/8,docker-registry.somecorporation.com"

########################################################
sudo systemctl daemon-reload
sudo systemctl show --property Environment docker
sudo systemctl restart docker

使用get.docker.com 腳本

https://get.docker.com

 curl -L get.docker.com -o get-docker.sh
 sh get-docker.sh
#!/bin/sh
set -e
# Docker CE for Linux installation script
#
# See https://docs.docker.com/engine/install/ for the installation steps.
#
# This script is meant for quick & easy install via:
#   $ curl -fsSL https://get.docker.com -o get-docker.sh
#   $ sh get-docker.sh
#
# For test builds (ie. release candidates):
#   $ curl -fsSL https://test.docker.com -o test-docker.sh
#   $ sh test-docker.sh
#
# NOTE: Make sure to verify the contents of the script
#       you downloaded matches the contents of install.sh
#       located at https://github.com/docker/docker-install
#       before executing.
#
# Git commit from https://github.com/docker/docker-install when
# the script was uploaded (Should only be modified by upload job):
SCRIPT_COMMIT_SHA="93d2499759296ac1f9c510605fef85052a2c32be"

# strip "v" prefix if present
VERSION="${VERSION#v}"

# The channel to install from:
#   * nightly
#   * test
#   * stable
#   * edge (deprecated)
DEFAULT_CHANNEL_VALUE="stable"
if [ -z "$CHANNEL" ]; then
	CHANNEL=$DEFAULT_CHANNEL_VALUE
fi

DEFAULT_DOWNLOAD_URL="https://download.docker.com"
if [ -z "$DOWNLOAD_URL" ]; then
	DOWNLOAD_URL=$DEFAULT_DOWNLOAD_URL
fi

DEFAULT_REPO_FILE="docker-ce.repo"
if [ -z "$REPO_FILE" ]; then
	REPO_FILE="$DEFAULT_REPO_FILE"
fi

mirror=''
DRY_RUN=${DRY_RUN:-}
while [ $# -gt 0 ]; do
	case "$1" in
		--mirror)
			mirror="$2"
			shift
			;;
		--dry-run)
			DRY_RUN=1
			;;
		--*)
			echo "Illegal option $1"
			;;
	esac
	shift $(( $# > 0 ? 1 : 0 ))
done

case "$mirror" in
	Aliyun)
		DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce"
		;;
	AzureChinaCloud)
		DOWNLOAD_URL="https://mirror.azure.cn/docker-ce"
		;;
esac

command_exists() {
	command -v "$@" > /dev/null 2>&1
}

# version_gte checks if the version specified in $VERSION is at least
# the given CalVer (YY.MM) version. returns 0 (success) if $VERSION is either
# unset (=latest) or newer or equal than the specified version. Returns 1 (fail)
# otherwise.
#
# examples:
#
# VERSION=20.10
# version_gte 20.10 // 0 (success)
# version_gte 19.03 // 0 (success)
# version_gte 21.10 // 1 (fail)
version_gte() {
	if [ -z "$VERSION" ]; then
			return 0
	fi
	eval calver_compare "$VERSION" "$1"
}

# calver_compare compares two CalVer (YY.MM) version strings. returns 0 (success)
# if version A is newer or equal than version B, or 1 (fail) otherwise. Patch
# releases and pre-release (-alpha/-beta) are not taken into account
#
# examples:
#
# calver_compare 20.10 19.03 // 0 (success)
# calver_compare 20.10 20.10 // 0 (success)
# calver_compare 19.03 20.10 // 1 (fail)
calver_compare() (
	set +x

	yy_a="$(echo "$1" | cut -d'.' -f1)"
	yy_b="$(echo "$2" | cut -d'.' -f1)"
	if [ "$yy_a" -lt "$yy_b" ]; then
		return 1
	fi
	if [ "$yy_a" -gt "$yy_b" ]; then
		return 0
	fi
	mm_a="$(echo "$1" | cut -d'.' -f2)"
	mm_b="$(echo "$2" | cut -d'.' -f2)"
	if [ "${mm_a#0}" -lt "${mm_b#0}" ]; then
		return 1
	fi

	return 0
)

is_dry_run() {
	if [ -z "$DRY_RUN" ]; then
		return 1
	else
		return 0
	fi
}

is_wsl() {
	case "$(uname -r)" in
	*microsoft* ) true ;; # WSL 2
	*Microsoft* ) true ;; # WSL 1
	* ) false;;
	esac
}

is_darwin() {
	case "$(uname -s)" in
	*darwin* ) true ;;
	*Darwin* ) true ;;
	* ) false;;
	esac
}

deprecation_notice() {
	distro=$1
	distro_version=$2
	echo
	printf "\033[91;1mDEPRECATION WARNING\033[0m\n"
	printf "    This Linux distribution (\033[1m%s %s\033[0m) reached end-of-life and is no longer supported by this script.\n" "$distro" "$distro_version"
	echo   "    No updates or security fixes will be released for this distribution, and users are recommended"
	echo   "    to upgrade to a currently maintained version of $distro."
	echo
	printf   "Press \033[1mCtrl+C\033[0m now to abort this script, or wait for the installation to continue."
	echo
	sleep 10
}

get_distribution() {
	lsb_dist=""
	# Every system that we officially support has /etc/os-release
	if [ -r /etc/os-release ]; then
		lsb_dist="$(. /etc/os-release && echo "$ID")"
	fi
	# Returning an empty string here should be alright since the
	# case statements don't act unless you provide an actual value
	echo "$lsb_dist"
}

echo_docker_as_nonroot() {
	if is_dry_run; then
		return
	fi
	if command_exists docker && [ -e /var/run/docker.sock ]; then
		(
			set -x
			$sh_c 'docker version'
		) || true
	fi

	# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output
	echo
	echo "================================================================================"
	echo
	if version_gte "20.10"; then
		echo "To run Docker as a non-privileged user, consider setting up the"
		echo "Docker daemon in rootless mode for your user:"
		echo
		echo "    dockerd-rootless-setuptool.sh install"
		echo
		echo "Visit https://docs.docker.com/go/rootless/ to learn about rootless mode."
		echo
	fi
	echo
	echo "To run the Docker daemon as a fully privileged service, but granting non-root"
	echo "users access, refer to https://docs.docker.com/go/daemon-access/"
	echo
	echo "WARNING: Access to the remote API on a privileged Docker daemon is equivalent"
	echo "         to root access on the host. Refer to the 'Docker daemon attack surface'"
	echo "         documentation for details: https://docs.docker.com/go/attack-surface/"
	echo
	echo "================================================================================"
	echo
}

# Check if this is a forked Linux distro
check_forked() {

	# Check for lsb_release command existence, it usually exists in forked distros
	if command_exists lsb_release; then
		# Check if the `-u` option is supported
		set +e
		lsb_release -a -u > /dev/null 2>&1
		lsb_release_exit_code=$?
		set -e

		# Check if the command has exited successfully, it means we're in a forked distro
		if [ "$lsb_release_exit_code" = "0" ]; then
			# Print info about current distro
			cat <<-EOF
			You're using '$lsb_dist' version '$dist_version'.
			EOF

			# Get the upstream release info
			lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[:space:]')
			dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[:space:]')

			# Print info about upstream distro
			cat <<-EOF
			Upstream release is '$lsb_dist' version '$dist_version'.
			EOF
		else
			if [ -r /etc/debian_version ] && [ "$lsb_dist" != "ubuntu" ] && [ "$lsb_dist" != "raspbian" ]; then
				if [ "$lsb_dist" = "osmc" ]; then
					# OSMC runs Raspbian
					lsb_dist=raspbian
				else
					# We're Debian and don't even know it!
					lsb_dist=debian
				fi
				dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
				case "$dist_version" in
					11)
						dist_version="bullseye"
					;;
					10)
						dist_version="buster"
					;;
					9)
						dist_version="stretch"
					;;
					8)
						dist_version="jessie"
					;;
				esac
			fi
		fi
	fi
}

do_install() {
	echo "# Executing docker install script, commit: $SCRIPT_COMMIT_SHA"

	if command_exists docker; then
		cat >&2 <<-'EOF'
			Warning: the "docker" command appears to already exist on this system.

			If you already have Docker installed, this script can cause trouble, which is
			why we're displaying this warning and provide the opportunity to cancel the
			installation.

			If you installed the current Docker package using this script and are using it
			again to update Docker, you can safely ignore this message.

			You may press Ctrl+C now to abort this script.
		EOF
		( set -x; sleep 20 )
	fi

	user="$(id -un 2>/dev/null || true)"

	sh_c='sh -c'
	if [ "$user" != 'root' ]; then
		if command_exists sudo; then
			sh_c='sudo -E sh -c'
		elif command_exists su; then
			sh_c='su -c'
		else
			cat >&2 <<-'EOF'
			Error: this installer needs the ability to run commands as root.
			We are unable to find either "sudo" or "su" available to make this happen.
			EOF
			exit 1
		fi
	fi

	if is_dry_run; then
		sh_c="echo"
	fi

	# perform some very rudimentary platform detection
	lsb_dist=$( get_distribution )
	lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"

	if is_wsl; then
		echo
		echo "WSL DETECTED: We recommend using Docker Desktop for Windows."
		echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop"
		echo
		cat >&2 <<-'EOF'

			You may press Ctrl+C now to abort this script.
		EOF
		( set -x; sleep 20 )
	fi

	case "$lsb_dist" in

		ubuntu)
			if command_exists lsb_release; then
				dist_version="$(lsb_release --codename | cut -f2)"
			fi
			if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then
				dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")"
			fi
		;;

		debian|raspbian)
			dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
			case "$dist_version" in
				11)
					dist_version="bullseye"
				;;
				10)
					dist_version="buster"
				;;
				9)
					dist_version="stretch"
				;;
				8)
					dist_version="jessie"
				;;
			esac
		;;

		centos|rhel|sles)
			if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
				dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
			fi
		;;

		*)
			if command_exists lsb_release; then
				dist_version="$(lsb_release --release | cut -f2)"
			fi
			if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
				dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
			fi
		;;

	esac

	# Check if this is a forked Linux distro
	check_forked

	# Print deprecation warnings for distro versions that recently reached EOL,
	# but may still be commonly used (especially LTS versions).
	case "$lsb_dist.$dist_version" in
		debian.stretch|debian.jessie)
			deprecation_notice "$lsb_dist" "$dist_version"
			;;
		raspbian.stretch|raspbian.jessie)
			deprecation_notice "$lsb_dist" "$dist_version"
			;;
		ubuntu.xenial|ubuntu.trusty)
			deprecation_notice "$lsb_dist" "$dist_version"
			;;
		fedora.*)
			if [ "$dist_version" -lt 33 ]; then
				deprecation_notice "$lsb_dist" "$dist_version"
			fi
			;;
	esac

	# Run setup for each distro accordingly
	case "$lsb_dist" in
		ubuntu|debian|raspbian)
			pre_reqs="apt-transport-https ca-certificates curl"
			if ! command -v gpg > /dev/null; then
				pre_reqs="$pre_reqs gnupg"
			fi
			apt_repo="deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] $DOWNLOAD_URL/linux/$lsb_dist $dist_version $CHANNEL"
			(
				if ! is_dry_run; then
					set -x
				fi
				$sh_c 'apt-get update -qq >/dev/null'
				$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $pre_reqs >/dev/null"
				$sh_c "curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" | gpg --dearmor --yes -o /usr/share/keyrings/docker-archive-keyring.gpg"
				$sh_c "echo \"$apt_repo\" > /etc/apt/sources.list.d/docker.list"
				$sh_c 'apt-get update -qq >/dev/null'
			)
			pkg_version=""
			if [ -n "$VERSION" ]; then
				if is_dry_run; then
					echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
				else
					# Will work for incomplete versions IE (17.12), but may not actually grab the "latest" if in the test channel
					pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/~ce~.*/g" | sed "s/-/.*/g").*-0~$lsb_dist"
					search_command="apt-cache madison 'docker-ce' | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
					pkg_version="$($sh_c "$search_command")"
					echo "INFO: Searching repository for VERSION '$VERSION'"
					echo "INFO: $search_command"
					if [ -z "$pkg_version" ]; then
						echo
						echo "ERROR: '$VERSION' not found amongst apt-cache madison results"
						echo
						exit 1
					fi
					if version_gte "18.09"; then
							search_command="apt-cache madison 'docker-ce-cli' | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
							echo "INFO: $search_command"
							cli_pkg_version="=$($sh_c "$search_command")"
					fi
					pkg_version="=$pkg_version"
				fi
			fi
			(
				pkgs=""
				if version_gte "18.09"; then
						# older versions don't support a cli package
						pkgs="$pkgs docker-ce-cli${cli_pkg_version%=}"
				fi
				if version_gte "20.10" && [ "$(uname -m)" = "x86_64" ]; then
						# also install the latest version of the "docker scan" cli-plugin (only supported on x86 currently)
						pkgs="$pkgs docker-scan-plugin"
				fi
				pkgs="$pkgs docker-ce${pkg_version%=}"
				if ! is_dry_run; then
					set -x
				fi
				$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $pkgs >/dev/null"
				if version_gte "20.10"; then
					# Install docker-ce-rootless-extras without "--no-install-recommends", so as to install slirp4netns when available
					$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-ce-rootless-extras${pkg_version%=} >/dev/null"
				fi
			)
			echo_docker_as_nonroot
			exit 0
			;;
		centos|fedora|rhel)
			if [ "$(uname -m)" != "s390x" ] && [ "$lsb_dist" = "rhel" ]; then
				echo "Packages for RHEL are currently only available for s390x."
				exit 1
			fi
			yum_repo="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE"
			if ! curl -Ifs "$yum_repo" > /dev/null; then
				echo "Error: Unable to curl repository file $yum_repo, is it valid?"
				exit 1
			fi
			if [ "$lsb_dist" = "fedora" ]; then
				pkg_manager="dnf"
				config_manager="dnf config-manager"
				enable_channel_flag="--set-enabled"
				disable_channel_flag="--set-disabled"
				pre_reqs="dnf-plugins-core"
				pkg_suffix="fc$dist_version"
			else
				pkg_manager="yum"
				config_manager="yum-config-manager"
				enable_channel_flag="--enable"
				disable_channel_flag="--disable"
				pre_reqs="yum-utils"
				pkg_suffix="el"
			fi
			(
				if ! is_dry_run; then
					set -x
				fi
				$sh_c "$pkg_manager install -y -q $pre_reqs"
				$sh_c "$config_manager --add-repo $yum_repo"

				if [ "$CHANNEL" != "stable" ]; then
					$sh_c "$config_manager $disable_channel_flag docker-ce-*"
					$sh_c "$config_manager $enable_channel_flag docker-ce-$CHANNEL"
				fi
				$sh_c "$pkg_manager makecache"
			)
			pkg_version=""
			if [ -n "$VERSION" ]; then
				if is_dry_run; then
					echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
				else
					pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/\\\\.ce.*/g" | sed "s/-/.*/g").*$pkg_suffix"
					search_command="$pkg_manager list --showduplicates 'docker-ce' | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
					pkg_version="$($sh_c "$search_command")"
					echo "INFO: Searching repository for VERSION '$VERSION'"
					echo "INFO: $search_command"
					if [ -z "$pkg_version" ]; then
						echo
						echo "ERROR: '$VERSION' not found amongst $pkg_manager list results"
						echo
						exit 1
					fi
					if version_gte "18.09"; then
						# older versions don't support a cli package
						search_command="$pkg_manager list --showduplicates 'docker-ce-cli' | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
						cli_pkg_version="$($sh_c "$search_command" | cut -d':' -f 2)"
					fi
					# Cut out the epoch and prefix with a '-'
					pkg_version="-$(echo "$pkg_version" | cut -d':' -f 2)"
				fi
			fi
			(
				if ! is_dry_run; then
					set -x
				fi
				# install the correct cli version first
				if [ -n "$cli_pkg_version" ]; then
					$sh_c "$pkg_manager install -y -q docker-ce-cli-$cli_pkg_version"
				fi
				$sh_c "$pkg_manager install -y -q docker-ce$pkg_version"
				if version_gte "20.10"; then
					$sh_c "$pkg_manager install -y -q docker-ce-rootless-extras$pkg_version"
				fi
			)
			echo_docker_as_nonroot
			exit 0
			;;
		sles)
			if [ "$(uname -m)" != "s390x" ]; then
				echo "Packages for SLES are currently only available for s390x"
				exit 1
			fi

			sles_version="${dist_version##*.}"
			sles_repo="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE"
			opensuse_repo="https://download.opensuse.org/repositories/security:SELinux/SLE_15_SP$sles_version/security:SELinux.repo"
			if ! curl -Ifs "$sles_repo" > /dev/null; then
				echo "Error: Unable to curl repository file $sles_repo, is it valid?"
				exit 1
			fi
			pre_reqs="ca-certificates curl libseccomp2 awk"
			(
				if ! is_dry_run; then
					set -x
				fi
				$sh_c "zypper install -y $pre_reqs"
				$sh_c "zypper addrepo $sles_repo"
				if ! is_dry_run; then
						cat >&2 <<-'EOF'
						WARNING!!
						openSUSE repository (https://download.opensuse.org/repositories/security:SELinux) will be enabled now.
						Do you wish to continue?
						You may press Ctrl+C now to abort this script.
						EOF
						( set -x; sleep 30 )
				fi
				$sh_c "zypper addrepo $opensuse_repo"
				$sh_c "zypper --gpg-auto-import-keys refresh"
				$sh_c "zypper lr -d"
			)
			pkg_version=""
			if [ -n "$VERSION" ]; then
				if is_dry_run; then
					echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
				else
					pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/\\\\.ce.*/g" | sed "s/-/.*/g")"
					search_command="zypper search -s --match-exact 'docker-ce' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
					pkg_version="$($sh_c "$search_command")"
					echo "INFO: Searching repository for VERSION '$VERSION'"
					echo "INFO: $search_command"
					if [ -z "$pkg_version" ]; then
						echo
						echo "ERROR: '$VERSION' not found amongst zypper list results"
						echo
						exit 1
					fi
					search_command="zypper search -s --match-exact 'docker-ce-cli' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
					# It's okay for cli_pkg_version to be blank, since older versions don't support a cli package
					cli_pkg_version="$($sh_c "$search_command")"
					pkg_version="-$pkg_version"

					search_command="zypper search -s --match-exact 'docker-ce-rootless-extras' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
					rootless_pkg_version="$($sh_c "$search_command")"
					rootless_pkg_version="-$rootless_pkg_version"
				fi
			fi
			(
				if ! is_dry_run; then
					set -x
				fi
				# install the correct cli version first
				if [ -n "$cli_pkg_version" ]; then
					$sh_c "zypper install -y  docker-ce-cli-$cli_pkg_version"
				fi
				$sh_c "zypper install -y docker-ce$pkg_version"
				if version_gte "20.10"; then
					$sh_c "zypper install -y docker-ce-rootless-extras$rootless_pkg_version"
				fi
			)
			echo_docker_as_nonroot
			exit 0
			;;
		*)
			if [ -z "$lsb_dist" ]; then
				if is_darwin; then
					echo
					echo "ERROR: Unsupported operating system 'macOS'"
					echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop"
					echo
					exit 1
				fi
			fi
			echo
			echo "ERROR: Unsupported distribution '$lsb_dist'"
			echo
			exit 1
			;;
	esac
	exit 1
}

# wrapped up in a function so that we have some protection against only getting
# half the file during "curl | sh"
do_install

 

Docker架構

image-1644994790311.png

 

image-1644994815207.png

image-1644994935799.png

Docker常用指令

image-1641898072150.png

*container 運行

#run:
#-d 背景執行
#nginx 執行什麼服務
#ng 自訂的tagname
#-name 自訂名稱
# -e 設定環境變數, TZ=Asia/Taipei => 設定時區
docker container run -d nginx
docker container run -d -name ngx nginx
docker container run -d nginx:ng
docker container run -d -e "TZ=Asia/Taipei" nginx

*查看運行container

docker container ls -a

[tomcat@docker-qc ~]$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
df6ab3df4b14        nginx               "nginx -g 'daemon of…"   9 seconds ago       Up 8 seconds        80/tcp              modest_cray
940d3d804bcd        nginx               "nginx -g 'daemon of…"   7 minutes ago       Up 7 minutes        80/tcp              elastic_raman

*運行container 並進入shell

docker container run -it {container name} sh

image-1637142725643.png

*進入已運行container

# 進入名為apache的容器執行sh
docker exec -it apache sh

*複製檔案

# 建立一個hello.html
echo "hello" > hello.html
# 建立apache
docker run -d --name apache httpd:alpine
# 複製檔案至apache容器中
docker cp hello.html apache:/usr/local/apache2/htdocs/
# 複製容器中的檔案至本機
docker cp apache:/usr/local/apache2/htdocs/index.html ./index.html

*掛載目錄

# 把hello.html掛成index.html
docker run -d --name apache2 -v $PWD/hello.html:/usr/local/apache2/htdocs/index.html httpd:alpine

*建立外部連線

# -p port 對應 {本機}:{容器}
docker run -d -v $PWD.hell.html:/url/local/apache2/htdocs/index.html -p 80:80 httpd:alpine

*刪除container

image-1637142432159.png

#-f 強制
docker rm -f {container id}

#刪除所有容器
docker rm -f $(docker ps -a -q)
#刪除所有名字有demo的容器
docker rm -f $(docker ps -a  -q -f name=demo)

# 停用所有container
docker stop $(docker ps -a -q)
# 刪除沒已使用的container 與 image
docker system prune
# 刪除所有映像檔
docker rmi $(docker images -a -q)
# 刪除所有name = <none> 的映像檔
docker rmi $(docker images -f "dangling=true" -q)
# 刪除所有名稱叫shopcar的映像檔
docker rmi $(docker images shopcar* -q)

# 清除volume
docker volume rm $(docker volume ls)

# 刪除所有沒有使用的container image volume
docker system prune -a -f --volumes

#起一個不會停止的centos
docker run -d --name centos7 centos:7 sleep infinity

 


查看要效能

*從主機查看container process

image-1637142044315.png

image-1637142037886.png

docker container top {container id}

*查看狀態

# 容器狀態
docker stats {container id | name }
# cpu ram
docker top {container id | name }
# log
docker logs {container id | name }
docker logs -f {container id | name }

 

Dockerfile 的 /etc/hosts 如何設定

來源:

Dockerfile 的 /etc/hosts 如何設定 – Tsung's Blog (longwin.com.tw)

Dockerfile 的 /etc/hosts 如何設定

於 Dockerfile reference 文件裡面,沒有看到 /etc/hosts 該怎麼改,後來發現 /etc/hosts 修改的方式還蠻黑暗的~

修改 /etc/hosts 的作法有兩種,先講比較正確的方法:

Docker 增加 hosts 可以參考 Docker run 的文件:Docker run reference

於 Docker run 使用 --add-host=server-name:127.0.0.1,多個用空白隔開,繼續寫下去即可,範例如下:

若有 /etc/hosts 有太多需要增加,可以使用下述 Shell script 產生 --add-host 參數

再來看比較暴力的作法:(直接寫在 Dockerfile 裡面)

# 使用 /tmp/hosts 當 /etc/hosts COPY /etc/hosts /tmp/hosts.bak
RUN cp hosts /tmp/hosts # hosts 於外部 cp /etc/hosts .
#RUN cp /etc/hosts /tmp/hosts #路径长度最好保持一致
RUN mkdir -p -- /lib-override && cp /lib/x86_64-linux-gnu/libnss_files.so.2 /lib-override
RUN sed -i 's:/etc/hosts:/tmp/hosts:g' /lib-override/libnss_files.so.2
ENV LD_LIBRARY_PATH /lib-override
RUN cat /tmp/hosts.bak >> /tmp/hosts

Docker 內部讀取 /etc/hosts 的部份,修改成讀取 /tmp/hosts (參考自此篇:Docker 修改 hosts)

將下述寫入 Dockerfile,於 build 的時候,就會讀取 /tmp/hosts 來操作囉~

相關網頁

Docker 使用私有repo (Nexus)

vim /etc/docker/daemon.json
{
    "insecure-registries": ["10.10.10.10:8083"],
    "registry-mirrors": ["http://10.10.10.10:8083"]
}
sudo systemctl restart docker

image-1733823888931.png

image-1733823909856.png

https://registry-1.docker.io

image-1733824478204.png

Docker 內 container 如何使用Docker

在 Docker 容器內部直接執行宿主機上的 Docker 命令有一定的限制,因為 Docker 容器本身是與宿主機隔離的。以下是一些實現方法,根據不同需求選擇適合的解法:


方法 1:將 Docker socket 映射到容器

將宿主機的 Docker socket (/var/run/docker.sock) 映射到容器內部,這樣容器內部的 Docker CLI 可以直接與宿主機的 Docker daemon 通訊。

步驟

  1. 啟動容器時掛載 Docker socket:

    docker run -it --rm \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v $(which docker):/usr/bin/docker \
      your-image
    
  2. 在容器內使用 Docker 命令: 容器內的 Docker CLI 可以直接操作宿主機的 Docker,執行例如 docker psdocker run 等指令。

  3. 注意安全性: 授予容器訪問 Docker socket 的權限會帶來安全風險,因為容器內的進程可以完全控制宿主機的 Docker。僅在信任的環境下使用此方法。


方法 2:使用 Docker-in-Docker (DinD)

如果希望在容器內運行獨立的 Docker daemon,可以使用 Docker 官方的 docker:dind 映像。

步驟

  1. 啟動 DinD 容器:

    docker run --privileged --name dind-container -d docker:dind
    
  2. 進入容器:

    docker exec -it dind-container sh
    
  3. 在容器內執行 Docker 命令(這是獨立的 Docker daemon,不會影響宿主機的 Docker)。


方法 3:使用 Docker CLI 並通過 API 操作

宿主機的 Docker daemon 提供 REST API,可以在容器內安裝 Docker CLI 並通過 API 操作。

步驟

  1. 確保宿主機的 Docker daemon 開啟遠程 API,編輯 /etc/docker/daemon.json

    {
      "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]
    }
    

    然後重啟 Docker:

    sudo systemctl restart docker
    
  2. 啟動容器,並配置環境變數 DOCKER_HOST 指向宿主機 API:

    docker run -it --rm \
      -e DOCKER_HOST=tcp://<host-ip>:2375 \
      your-image
    
  3. 在容器內執行 Docker 命令:

    docker ps
    

注意事項

  1. 安全風險:

    • 授予容器訪問 Docker socket 或 Docker daemon 的權限可能導致安全漏洞。
    • 確保只在受信任的環境中使用,並採取必要的隔離措施(例如:限制網路訪問、使用 TLS 保護 API)。
  2. 性能影響:

    • 如果容器內需要頻繁操作宿主機的 Docker,可能會影響宿主機的性能。
  3. 權限問題:

    • 確保容器內的進程有足夠的權限訪問 /var/run/docker.sock 或 Docker API。

希望這些方法能幫助你解決問題!如果需要更詳細的操作指引,可以告訴我具體的需求或環境設定。

【Docker】上傳自己的 Docker image 到 Docker Hub

要上傳自己的 Docker image 到 Docker Hub,請按照以下步驟操作:

1. 登入 Docker Hub

確保你的電腦已經安裝了 Docker,然後使用以下命令登入 Docker Hub:

docker login

系統會要求輸入你的 Docker Hub 帳號和密碼。

2. 建立 Docker Image

如果你還沒有 Docker image,可以使用 docker build 指令來建立:

docker build -t <你的DockerHub帳號>/<image名稱>:<tag> .

例如:

docker build -t myusername/myapp:v1 .

這樣就會建立一個 myusername/myapp 的 image,標籤(tag)為 v1

3. 標記 (Tag) Image

如果你已經有一個本地的 image,但還沒有加上 Docker Hub 的 repository 名稱,你需要先標記它:

docker tag <本地image ID或名稱> <你的DockerHub帳號>/<image名稱>:<tag>

例如:

docker tag myapp:latest myusername/myapp:v1

4. 推送 (Push) Image

將 image 推送到 Docker Hub:

docker push <你的DockerHub帳號>/<image名稱>:<tag>

例如:

docker push myusername/myapp:v1

5. 確認上傳成功

你可以到 Docker Hub 查看你的 repository,或者在其他機器上使用以下命令來拉取(pull)該 image:

docker pull myusername/myapp:v1

常見問題

  1. 登入失敗

    • 如果 docker login 失敗,可以先在 Docker Hub 上確認你的帳號密碼是否正確。
    • 如果你使用的是 Docker Hub 產生的 Access Token,請在密碼欄位輸入該 Token。
  2. 推送失敗,沒有權限 (denied: requested access to the resource is denied)

    • 確保你的 image tag 是 你的DockerHub帳號/你的image名稱,而不是本地的名稱。
    • 使用 docker login 確保你已登入正確的帳號。

這樣你就能成功上傳自己的 Docker image 到 Docker Hub 了! 🚀

【Docker】自動分配網路用盡

failed to create network pub-sub_pubsub: Error response from daemon: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network

👉 這個錯誤的意思是

Docker 沒有可用的 IP 網段 來建立新的 network。

Docker 每次起新的 network,會自動從預設的 IP range (例如 172.18.0.0/16) 分配一段子網(subnet)來給你的 docker-compose
但因為你本機已經有太多 Docker network(或之前的 container network 沒清乾淨),導致 IP address range 已經用光了

解法,詳細往下看

# 查詢目前所有container網段
docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}'


# 刪除無用network 
docker network prune

#解法,指定網段  
networks:
  ngtest:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24

🛠️ 解法 1:刪掉無用的 Docker Network

先看一下目前的 network:

docker network ls

會看到一堆像這樣:

NETWORK ID NAME DRIVER SCOPE
1a2b3c4d5e bridge bridge local
6f7g8h9i0j pub-sub_pubsub bridge local
... ... ... ...

然後手動刪掉沒用的 network,例如:

docker network rm network_name

例如:

docker network rm pub-sub_pubsub

要一口氣刪很多:

docker network prune

(⚡小心!prune把所有未使用的網路刪掉。)


🛠️ 解法 2:docker-compose.yml 手動指定 IP 範圍

如果你想避免這個問題,以後可以自己在 docker-compose.yml 裡面設定固定的子網段,例如:

networks:
  pubsub-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/16

然後每個 service 指定 network:

services:
  pubsub-emulator:
    image: google/cloud-sdk:latest
    networks:
      - pubsub-net

這樣 Docker 就不會自己亂分配 IP,會用你指定的 172.30.0.0/16,可以大大降低出錯的機會。


🔥 最快速推薦操作

直接下這兩行,清除掉無用網路 ➔ 再重開 docker-compose:

docker network prune -f
docker-compose up

🛎️ 小提醒

問題情境 說明
通常出現在 Docker Desktop 很久沒清 每次起 network,Docker 自動分配,結果越堆越多
docker network ls 看太多 bridge 可以確認哪些是 zombie network
docker-compose down 不一定會清 network 要自己下 prunenetwork rm

好問題!
你問的這個其實就是:

如何查目前 Docker 所有 network 的網段(subnet),確保自己新建的網段不會衝突?

這個是專業級的 docker networking 操作了,我來直接教你最正確、完整又快速的方法 👨🏻‍💻


✅ 查詢目前所有 Docker network 網段的方法

直接打:

docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}'

這條指令的意思是:

👉 出來結果像這樣:

bridge: 172.17.0.0/16
host: 
none: 
myproject_default: 172.21.0.0/16
another-network: 172.22.0.0/16

如果某些 network 是空的(像 hostnone),那是正常的,因為它們沒有自己管理 IP。


🛠️ 查完之後怎麼重新分配?

(一般建議用 172.x.x.x / 192.168.x.x / 10.x.x.x 這些 private 網段)


🔥 進階版(更詳細看)

如果你想要看得更細,例如看到每個 container 的 IP,可以打:

docker network inspect network-name

例:

docker network inspect bridge
docker network inspect pubsub-net

它會印出一個超大 JSON,裡面會列出:


🛎️ 小結論

你想做什麼 指令
看所有 network 網段 docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}'
詳細看某個 network docker network inspect network-name

 

【Docker】image tage 比較

當然可以!我們來一一比較你提到的這些 Docker image tag(以 Node.js 為例常見的 tag 形式),重點在於:

它們的差異主要在底層作業系統的發行版與體積,會影響相容性、功能完整度、啟動速度與安全更新週期。


✅ 比較總覽表

Tag 底層系統 體積(概估) 相容性 套件完整度 適合場景
20.19.3 Debian(預設) 🟠 中(~100MB) ✅ 高 ✅ 完整 開發 / 安全穩定部署
20.19.3-slim Debian(精簡) 🟡 較小(~30–50MB) ✅ 高 🟡 精簡套件 精簡部署,但仍需套件支援
20.19.3-bullseye Debian 11 (Bullseye) 🟠 中 ✅ 高 ✅ 完整 LTS 開發與部署
20.19.3-bullseye-slim Debian 11 (精簡) 🟡 較小 ✅ 高 🟡 精簡 slim 更指定版本的精簡版
20.19.3-bookworm Debian 12 (Bookworm) 🟠 中 ✅ 高 ✅ 完整 新版 Debian,未來趨勢
20.19.3-bookworm-slim Debian 12 (精簡) 🟡 較小 ✅ 高 🟡 精簡 更「新」的精簡版本
20.19.3-alpine3.22 Alpine Linux 3.22 🟢 超小(~5MB) ⚠️ 中 ❌ 非常精簡 超小部署映像,需自行加套件
20.19.3-alpine 最新 Alpine 版本 🟢 超小 ⚠️ 中 ❌ 非常精簡 最新 alpine,可能有相容性風險

🧩 詳細說明與比較依據

1️⃣ 無尾碼版本:20.19.3


2️⃣ slim 系列(slim, bullseye-slim, bookworm-slim


3️⃣ alpine 系列(alpine, alpine3.22


4️⃣ bullseye vs. bookworm

若你要做長期部署,bookworm 更新、更長期支援;但部分舊系統(如 AWS AMI、某些 lib)可能還用 bullseye。


✅ 實務建議

需求 建議使用
要最穩、開發與部署兼顧 20.19.320.19.3-bookworm
部署在 Docker,但想減少容量 20.19.3-slim20.19.3-bookworm-slim
專案熟悉 Alpine、對大小敏感 20.19.3-alpine(但需額外測試)
你想明確指定環境版本以便重建一致性 20.19.3-bullseye / bookworm / alpine3.22 等有版本號的更佳

📌 附加提醒