Docker

可以將應用程式、依賴打包成一個映像檔 (image) ,並在隔離環境內運行 (container)

  • 確保環境一致 (dev / test / prod)
  • 簡化部署
  • 提升 CI/CD 效率

Environment

Installation

sudo usermod -aG docker <user>

 

把使用者加進去 docker group

跑一個 container 起來看看

services:
  nginx:
    image: nginx
    ports:
    - 80:80
  • ~/project/
    • docker-compose.yml

docker compose up

Image

  • 建立 container 用的 template
  • 包含所有所需運行 container 的檔案
  • read-only, 要修改 Image,只能再 build 一個

放在 Docker Hub 上的 Image:

https://hub.docker.com/search

  • Image 由多個 Layer 疊加而成
  • 每 Layer 代表一次檔案系統變更
    • (每次 build 產生的 file diff)
  • Image Layer 可被多個 Image 共用
  • 減少重複儲存
  • 提升 build cache 效率,加快 build、deploy
  • docker image ls

  • docker image inspect <image>

  • docker pull <image>

列出所有 host 上有的 image

查看某個 image 的 info

從 remote 拉取 image

常用指令

Container

隔離的執行環境

  • 共享 Host Kernel
  • 獨立的 process / network / fs
  • 被資源限制
  • Cgroup (Control Group)
  • Namespace

使用 Linux 的技術

  • VM
    • VM 有自己的 OS + Kernel
    • 啟動慢,佔用空間大
    • Hardware-level
  • Container
    • Shared Host Kernel
    • 啟動快,佔用空間極小
    • OS-level
  • 設計上,container 是短暫存在的,可被輕易刪除
  • 檔案系統變動 只會在 container layer (r/w layer)
    • container 一刪除,變更就會消失
      • (但可以使用外部機制做 persistant)

特性

  • docker container ls

    • alias: docker ps

    • -a (也列出 stopped containers)

列出所有 host 上的 running containers

 

 

常用指令

  • image
    • 用來建立 container
  • container
    • image 的執行結果

Compose

透過 YAML 設定檔,描述整個應用架構

  • 一次啟動多個 container
  • 方便處理網路、依賴、配置
services:
  frontend:
    build: ./frontend
    ports:
      - "3000:3000"

  backend:
    build: ./backend
    ports:
      - "8080:8080"
    environment:
      DB_HOST: db
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:
  • docker compose stop

  • docker compose down

  • docker compose up

    • -d (deattach console)

停止 container

停止 & 刪除 container

開啟 container

 

常用指令

Storage

container 的 persistant 方式分成

  • Bind Mount
  • Volume

Bind Mount

  • 把 host 上的檔案或資料夾直接 mount 到 container 中
  • container 可以直接讀寫該檔案
  • container 被刪除,host 上檔案仍然會存在
  • 開發時同步程式碼
  • 同步設定檔
  • 總之,想直接從 host 修改資料時

什麼時候用?

  • 因為 container 可以直接操作 host 上的檔案,可能會影響其他 process
  • 注意有沒有檔案權限問題
  • 可以用 :ro 避免 container 修改 host 檔案

注意事項

嘗試更改 nginx 的設定檔

  • nginx configuration
    • 在 /etc/nginx/ 內
    • 主要設定檔是 /etc/nginx/nginx.conf
  • 如果不知道裡面長怎樣,先進去 container 看看
    • docker exec -it <container> bash
    • cd /etc/nginx/
    • ls
  • nginx.conf 的 http block 會 include conf.d/*
    • 所以我們去 conf.d/ 看,應該會看到一個 default.conf
    • 他就是預設的 nginx 頁面。
  • 想要修改 default.conf,先在 container 外建立 default.conf
  • 再 mount 到對應的位置。
server {
    listen 80;
    server_name localhost;

    location / {
        add_header Content-Type "text/plain";
        return 200 "Hello!!!\n";
    }
}
  • ~/project/
    • default.conf
    • docker-compose.yml

 

docker compose down

關 & 刪 container

mount 過去

services:
  nginx:
    image: nginx
    ports:
    - 80:80
    volumes:
    - ./default.conf:/etc/nginx/conf.d/default.conf

 

docker compose up -d

開 container

Volume

  • 由 Docker 管理的 volume,不直接綁定 host 路徑
    • 在 /var/lib/docker/volumes/<vol_name>
    • 資料在 /var/lib/docker/volumes/<vol_name>/_data/
    • 難以存取
  • 可以被多個容器共用
  • production 環境的資料儲存
  • 不想要綁實體路徑,只想要持久化
  • 想方便移植

什麼時候用?

來開一個 pgSQL

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: testdb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

跑起來 (寫進 docker-compose.yml)

再來,我們從 host 連去 pgSQL 看看

 

安裝 postgres client

  • sudo apt update
  • sudo apt install -y postgresql-client

連進去

  • psql -h 127.0.0.1 -U admin -d testdb
  • CREATE TABLE test (content TEXT);

  • INSERT INTO test (content) VALUES ('a'), ('b');

  • SELECT * FROM test;

  • exit

建表

插資料

讀資料

離開 postgresql-client

 

docker compose down && docker compose up -d

​psql -h 127.0.0.1 -U admin -d testdb​

刪&關&開

連進去

  • \l

  • \c testdb

  • \dt

  • SELECT * FROM test;

list db

切到 testdb

list tables

讀資料

  • docker volume ls

  • docker volume inspect <vol>

  • docker volume rm <vol>

列出所有 host 上有的 volume

查看某個 volume 的 info

刪除 volume

常用指令

Dockerfile

包含一步驟的指令,用於建立 image

  • FROM <image>

  • RUN <command>

  • WORKDIR <dir>

  • COPY <src> <dest>

  • CMD <command>

你的 base image

建立新 layer,執行指令

設定 Dockerfile 的 working dir

把 host 上的檔案複製到 image

container 跑起來的時候,跑的指令

我們目前沒有東西可以打包成 image,我們先來寫東西吧

import express from "express";
import pg from "pg";

const app = express();

const client = new pg.Client({
  host: "127.0.0.1",
  user: "admin",
  password: "password",
  database: "testdb",
});

await client.connect();

app.get("/", async (req, res) => {
  const result = await client.query("SELECT * FROM test");
  res.json(result.rows);
});

app.listen(3328, '0.0.0.0', () => {
  console.log("Server running on 3328");
});

連接到 testdb

在 / 回傳 test 內的資料

  • ~/project/
    • docker-compose.yml
    • default.conf
    • index.js
{
  "dependencies": {
    "express": "^5.2.1",
    "pg": "^8.20.0"
  }
}
  • ~/project/
    • docker-compose.yml
    • default.conf
    • index.js
    • package.json

幫你們跑過了,會長這樣

FROM node:20

WORKDIR /app

COPY package.json ./
RUN npm install

COPY . .

EXPOSE 3328

CMD ["node", "index.js"]
  • ~/project/
    • default.conf
    • docker-compose.yml
    • Dockerfile
    • index.js
    • package.json

docker build -t web .

Build Image

-t 代表 tag (幫 image 命名)

docker image ls

把 web 加進去 compose

services:
  nginx:
    image: nginx
    ports:
    - 80:80
    volumes:
    - ./default.conf:/etc/nginx/conf.d/default.conf

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: testdb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: web
    ports:
      - "3328:3328"

volumes:
  pgdata:

 

docker compose up -d

開 container

It's fucked up.

我們先不管這個問題,至少他跑起來

build 區塊

  • 可以告訴 compose 怎麼建置 image
  • 用什麼 Dockerfile (預設: Dockerfile)
services:
  nginx:
    image: nginx
    ports:
    - 80:80
    volumes:
    - ./default.conf:/etc/nginx/conf.d/default.conf

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: testdb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: web
    build:
      context: .
    ports:
      - "3328:3328"

volumes:
  pgdata:

在 up 的時候要求重新 build image

docker compose up --build

再來,看看我們的 image,看看有什麼問題

他超大的

Multi-Stage Build

FROM node:20

WORKDIR /app

COPY package.json ./
RUN npm install

COPY . .

EXPOSE 3328

CMD ["node", "index.js"]
FROM node:20 AS builder

WORKDIR /app

COPY package.json .
RUN npm install


FROM node:20-slim

WORKDIR /app

COPY --from=builder /app/node_modules ./node_modules

COPY . .

EXPOSE 3328

CMD ["node", "index.js"]

也可以用 .dockerignore 避免打包不要的東西進去,像是 .git/

Network

Bridge

Bridge

  • host 上會有一個 virtual bridge "docker0"
  • 透過 veth pair 接入 bridge

 

container

  • 可以透過 NAT 連網
  • 有一張 eth0
  • IP 由 docker DHCP 分配

Docker0

Docker embedded DNS

service name 會被解析成 container ip

 

所以,同個 network 內,可用 service name 作為 domain 跟其他 container 連線

address: 127.0.0.11

(127.0.0.0/8 是 loopback)

題外話 (nginx settings)

回到我們的 docker-compose.yml

services:
  nginx:
    image: nginx
    ports:
    - 80:80
    volumes:
    - ./default.conf:/etc/nginx/conf.d/default.conf

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: testdb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: web
    ports:
      - "3328:3328"

volumes:
  pgdata:

如果不寫 networks

會自動建立一個 (bridge)

自動包含所有 services

所以 web 連線到 postgres, 會用 "db" 這個 DN

services:
  app:
    networks:
      - frontend

  api:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend
      
networks:
  frontend:
  backend:

自己寫 networks 通常用於網路隔離

Host

  • 直接用 host network stack
  • 不能 -p port mapping
    • 像是前面的 index.js,他會直接 bind host:3328
  • 沒有 Docker embedded DNS
    • 直接用 host DNS resolver

Registry

集中儲存、管理、分發 Image 的地方

像是 GitHub 儲存 git repo 一樣

  • 預設是 docker.io

Push Image

註冊 docker.com

Push Image

建立 PAT

Push Image

docker login docker.io -u <username>

Push Image

建立 Repository

Push Image

重新 Build Dokcer Image

因為我們的 repo 名稱是 <username>/web

所以 image 也要同名

Push Image

docker push <image>

services:
  nginx:
    image: nginx
    ports:
    - 80:80
    volumes:
    - ./default.conf:/etc/nginx/conf.d/default.conf

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: testdb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: speedcubing/web
    ports:
      - "3328:3328"

volumes:
  pgdata:

這時,我們的 image 如果改好

他就會去 Dockerhub pull

Github Container Registry

GitHub 用於存放 Docker Image 的服務

Github Container Registry

docker.io -> ghcr.io

結束

docker

By speedcubing