身為一個天天在跟容器打交道的人
我覺得Docker是每個搞後端或運維的人都應該要會的東西
今天就來從頭介紹一下Docker到底是什麼以及怎麼用吧

# 前言

如果你有在自架伺服器或是搞後端
應該多少都聽過Docker這個名字
但每次聽別人講什麼容器化、映像檔、Docker Compose
可能還是會覺得有點抽象

這篇文章會從最基本的概念開始
一路講到你可以自己寫Dockerfile和docker-compose.yml
目標是讓你看完之後可以自己把一個服務容器化並跑起來

# 先備條件

  • 你會用終端機(CLI)
  • 你有一台Linux、MacOS或Windows的電腦
  • 你知道什麼是伺服器(至少概念上知道)
  • 你的腦袋是清醒的

# Docker是什麼

# 虛擬機 vs 容器

在Docker出現之前
如果你想要在一台機器上跑多個獨立的服務
通常的做法是開虛擬機(VM)
每台VM都有自己完整的作業系統、核心、驅動
很安全很隔離 但也很肥很慢

Docker的做法不一樣
它不會去虛擬化整個作業系統
而是利用Linux核心的namespace和cgroup機制
把程式跑在一個隔離的環境裡 但共用同一個核心
所以啟動快、佔資源少、部署方便

簡單講
VM是幫你蓋一棟新房子 裡面有自己的水電瓦斯
Container是在同一棟大樓裡隔出一間套房 共用水電但有自己的門鎖

# 核心概念

在開始之前 先認識幾個名詞

名詞 說明
Image(映像檔) 一個唯讀的模板,包含了跑起一個服務所需的所有東西
Container(容器) 由Image啟動的一個實例,可以理解為一個在跑的程式
Dockerfile 一份描述如何建構Image的腳本
Docker Compose 用YAML定義多個容器的編排工具
Registry 存放Image的倉庫,最常見的是Docker Hub
Volume 資料卷,讓容器可以持久化資料

# 安裝Docker

# Linux

大部分的Linux發行版都可以用官方的安裝腳本一鍵搞定

curl -fsSL https://get.docker.com | sh

裝完之後把自己加進docker群組 這樣就不用每次都sudo

sudo usermod -aG docker $USER

加完群組之後要重新登入才會生效
你可以用newgrp docker暫時切換 或者直接重開終端

# MacOS

MacOS的話直接去Docker官網下載Docker Desktop
安裝完就可以用了 它會自帶一個Linux VM在背景跑

如果你是Apple Silicon(M系列晶片)
Docker Desktop會自動幫你處理arm64的相容性
但有些Image可能只有amd64版本 跑起來會透過Rosetta轉譯 效能會差一點

# Windows

Windows的話一樣去官網下載Docker Desktop
不過你需要先啟用WSL2

wsl --install

裝完重開機 再裝Docker Desktop就可以了

# 驗證安裝

不管哪個系統 裝完之後跑一下這個確認有沒有裝好

docker --version
docker run hello-world

如果看到一坨Hello from Docker!的訊息就代表成功了

# 基本操作

# 拉取Image

# 從Docker Hub拉取一個Image
docker pull nginx

# 拉取指定版本
docker pull nginx:1.25

# 看看本地有哪些Image
docker images

# 啟動Container

# 最基本的啟動方式
docker run nginx

# 背景執行 + 端口映射
docker run -d -p 8080:80 nginx

# 給容器取名字
docker run -d -p 8080:80 --name my-nginx nginx

解釋一下參數

參數 說明
-d 背景執行(detach)
-p 8080:80 把主機的8080 port映射到容器的80 port
--name 給容器取名字 不然Docker會隨機生成一個

跑完之後打開瀏覽器連http://localhost:8080
應該就能看到Nginx的預設頁面了

# 管理Container

# 列出正在跑的容器
docker ps

# 列出所有容器(包含已停止的)
docker ps -a

# 停止容器
docker stop my-nginx

# 啟動已停止的容器
docker start my-nginx

# 刪除容器(要先停止)
docker rm my-nginx

# 強制刪除(不用先停止)
docker rm -f my-nginx

# 進入Container

有時候你需要進到容器裡面看看到底發生了什麼事

# 進入容器的shell
docker exec -it my-nginx bash

# 如果容器沒有bash 可以試試sh
docker exec -it my-nginx sh

-it-i(interactive)加-t(tty)的縮寫
簡單講就是讓你可以跟容器互動

# 看Log

# 看容器的log
docker logs my-nginx

# 持續追蹤log(像tail -f一樣)
docker logs -f my-nginx

# 只看最後100行
docker logs --tail 100 my-nginx

# Dockerfile

到目前為止我們都是用別人做好的Image
但如果你要把自己的程式容器化 就需要自己寫Dockerfile

# 基本結構

# 基底Image
FROM node:20-slim

# 設定工作目錄
WORKDIR /app

# 複製檔案
COPY package.json package-lock.json ./

# 執行指令
RUN npm ci --production

# 複製其餘程式碼
COPY . .

# 暴露端口
EXPOSE 3000

# 啟動指令
CMD ["node", "server.js"]

# 常用指令說明

指令 說明
FROM 指定基底Image
WORKDIR 設定工作目錄 之後的指令都會在這個目錄下執行
COPY 複製檔案到容器裡
RUN 在build時執行指令(裝套件之類的)
EXPOSE 宣告容器要監聽的port(只是文件化 不會自動映射)
CMD 容器啟動時要執行的指令
ENV 設定環境變數
ARG 設定build時的參數

# Build Image

# 在有Dockerfile的目錄下
docker build -t my-app:latest .

# -t 是tag的意思 給你的Image取名字和版本

# .dockerignore

.gitignore一樣的概念
告訴Docker在COPY的時候要忽略哪些檔案

node_modules
.git
.env
*.md

一定要記得加.dockerignore
不然你COPY的時候會把node_modules之類的垃圾一起複製進去
不僅build變慢 Image也會變超肥

# Multi-stage Build

如果你的程式需要編譯(像TypeScript、Go之類的)
可以用multi-stage build來減少最終Image的大小

# Stage 1: Build
FROM node:20-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

第一個stage負責編譯 第二個stage只複製編譯完的結果
這樣最終的Image就不會包含devDependencies和原始碼

# Docker Compose

當你的服務不只一個容器的時候
一個一個docker run會瘋掉
Docker Compose就是來解決這個問題的

# 基本結構

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=db
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=mysecretpassword
      - POSTGRES_DB=myapp

volumes:
  db_data:

# 常用指令

# 啟動所有服務(背景執行)
docker compose up -d

# 停止所有服務
docker compose down

# 停止並刪除volume(小心資料會不見)
docker compose down -v

# 重新build並啟動
docker compose up -d --build

# 看log
docker compose logs -f

# 只看某個服務的log
docker compose logs -f web

# Volume

Volume是Docker用來持久化資料的機制
如果你不掛Volume 容器一刪資料就沒了

services:
  db:
    image: postgres:16
    volumes:
      # Named volume(Docker管理)
      - db_data:/var/lib/postgresql/data
      # Bind mount(直接掛主機目錄)
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  db_data:

Named volume和bind mount的差別在於
Named volume是Docker自己管理的 放在/var/lib/docker/volumes/底下
Bind mount是直接把主機的目錄映射進容器
一般來說 資料庫之類的用named volume 設定檔之類的用bind mount

# Network

Docker Compose預設會建立一個bridge network
同一個compose檔案裡的服務可以用服務名稱互相連線
也就是為什麼上面的DB_HOST可以直接寫db

如果你需要讓不同compose檔案的容器互相溝通
可以自己建立外部network

services:
  web:
    networks:
      - shared

networks:
  shared:
    external: true
# 先建立外部network
docker network create shared

# 常用技巧

# 清理垃圾

Docker用久了會堆積一堆沒在用的Image、Container、Volume
定期清一下

# 清除所有沒在用的東西(小心使用)
docker system prune -a

# 只清沒在用的Image
docker image prune -a

# 只清沒在用的Volume
docker volume prune

# 看看Docker佔了多少空間
docker system df

# 健康檢查

services:
  web:
    build: .
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

加了healthcheck之後 Docker會定期去戳你的服務確認它還活著
搭配restart: unless-stopped可以讓掛掉的服務自動重啟

# 環境變數管理

不要把密碼直接寫在docker-compose.yml裡面
.env檔案管理

# .env
POSTGRES_PASSWORD=mysecretpassword
DB_NAME=myapp
services:
  db:
    image: postgres:16
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${DB_NAME}

Docker Compose會自動讀取同目錄下的.env檔案

記得把.env加進.gitignore
不要把密碼推上去 這是常識

# 結語

Docker這東西說難不難 說簡單也沒那麼簡單
但只要搞懂Image、Container、Volume、Network這幾個核心概念
剩下的就是看文件然後多用就會了

我自己從開始用Docker到現在
已經離不開這東西了
不管是開發環境還是正式環境
有Docker在 環境問題基本上就不是問題了
「在我的電腦上可以跑」這句話也終於可以變成「在Docker裡可以跑」

之後如果有機會再來寫Docker在K8s上的應用吧
<!-- 反正我已經寫了一篇K8s MailServer了 -->

Total Views: Loading...Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

Miyago9267 WeChat Pay

WeChat Pay

Miyago9267 Alipay

Alipay

Miyago9267 buymeacoffee

buymeacoffee