點燈坊

失くすものさえない今が強くなるチャンスよ

深入淺出 Dockerfile 與 Docker Compose

Sam Xiao's Avatar 2021-10-30

Dockerfile 與 Docker Compose 是 Docker 兩個最重要的概念,也是初學者最容易卡關的地方,本文以 使用需求 為觀點解釋這兩者的差異。

Image 與 Container

在解釋 Dockerfile 與 Docker Compose 之前,先複習兩個更基礎的概念:

Image

將 service 打包成 image,通常會從 Docker Hub 下載官方的 image 使用,也可以根據官方的 image 再包成自己的 image,或者完全自行製作自己的 image。

Container

Image 類似 template,基於壓好的 image 產生隔離的執行環境,稱之為 container。

一般會以 microservice 方式使用 container,也就是會同時啟動多個 service,每個 service 以 container 形式啟動。

Dockerfile

實務上我們會從 Docker Hub 下載官方的 Docker image 使用,但官方的 image 功能可能過於陽春,我們可能想根據自己的需求,再安裝其他的 app / package / dependency,最後再打包成自己的 Docker image。

在傳統 VM 時代,要打造自己的 image,必須用 export 方式,但這樣有幾個缺點:

  • Image 可能非常龐大
  • 安裝步驟無法進 git 版控

Docker 以 Infrastructure as Code 概念,將 infrastructure 以 code 形式描述:

dockerfile

FROM node:latest
RUN yarn global add @vue/cli

以上為典型的 dockerfile,只需一個文字檔,就清楚描述一個 Docker image。

以前若要在 Linux 安裝其他 dependency,只能透過 Bash 安裝一堆 package,最後再 export 成 image,但 image 可能很龐大,且安裝步驟也無法 git 版控。

但透過 dockerfile 之後,整個新的 Linux 都是以 code 形式描述,你只要將 dockerfile 給其他人,對方只要使用 docker build 就能自行根據 dockerfile 建立 Docker image,最後再以 docker run 執行客製化過的 Ubuntu container。

  • dockerfile 檔案很小,只是文字檔而已
  • 由於 dockerfile 是文字檔,可以 git 版控

簡單的說,dockerfile 就是描述如何產生客製化的 image,只是採用 code 形式描述

Docker Compose

實務上後端服務,一定由眾多 service 共同運作。如一個典型的 Web 服務,以 .NET Core 為例,最少就必須有

.NET Core Runtime + Nginx + Redis + PosgreSQL

4 個 service 一起運行,若只使用 docker run,則勢必寫 Bash 來管理 4 個 service,還必須考慮:

  • 4 個 service 必須在同一個虛擬 network 下
  • 4 個 service 哪些檔案需與 host 共用
  • 4 個 service 的啟動順序

… 等問題。

Docker 為此提出 Docker Compose 概念,在 docker-compose.yml 檔描述各 service 間的參數與關係:

docker-compose.yml

version: "3"

services:
  netcore:
    image: microsoft/dotnet
    container_name: MyNETCore
    volumes:
      - ${NETCORE_HOST_DIR}:/code/
    tty: true
    networks: 
      - netcore-dev
    depends_on:
      - postgres

  postgres:
    image: postgres
    container_name: MyPostgres
    volumes:
      - ${POSTGRES_HOST_DIR}/data:/var/lib/postgresql/data
    expose:
      - "5432"
    ports:
      - "${POSTGRES_PORT}:5432"
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    networks:
      - netcore-dev

networks:
  netcore-dev:

以上為典型的 docker-compose.yml,只需一個文字檔,就清楚描述 .NET Core Runtime 與 PostgreSQL 兩個 service,將 docker-compose.yml 拿到任何裝有 Docker 的電腦,都能重現 .NET Core + PostreSQL 的環境,也就是 Infrastructure as Code

  • docker-compose.yml 檔案很小,只是文字檔而已
  • 由於 docker-compose.yml 是文字檔,可以 git 版控

間單的說,docker-compose.yml 就是 container 的管理文件,只是採用 code 形式描述

FAQ

Q:dockerfiledocker-compose.yml 看起來很像,都是在描述 server,有什麼差別呢 ?

  • dockerfile 是用來描述 image,也就是如何產生客製化的 image,通常用來安裝 package / dependency,將檔案複製進 image 用
  • docker-compose.yml 是用來描述 container,也就是管理一個以上的 container,彼此串連,把同一組架構寫在一起,通常用來設定 container 參數,設定 container 的網路,設定 container 啟動順序或 service 環境變數 … 等

Q:dockerfile 是用來描述 image,docker-compose.yml 是用來描述 container,但若我們還有客製化的 邏輯 該怎麼辦 ?

若使用 dockerfiledocker-compose.yml 時,還必須搭配額外的 if elsefor loop 邏輯,就必須再搭配 Bash 或 Makefile。

dockerfiledocker-composer.yml 只是設計用來描述 infrastructure,並不是描述 邏輯

Q:單一 service 也適用 docker-compose.yml 嗎 ?

由於實務上,儘管是單一 service,如只為了使用 PostgreSQL,也必須在 docker run 搭配一堆參數,但人的腦容量有限,很難記住所有的參數,與其寫在 Bash,建議寫在 docker-compose.yml ,統一由 docker-compose 管理。

Conclusion

  • dockerfile 用來描述 image;而 docker-compose.yml 用來描述 container
  • dockerfiledocker-compose.yml 還無法達到客製化的需求,就必須搭配 Bash 或 Makefile 處理邏輯部分
  • Dockerfiledocker-compose.yml 都是文字檔,因此檔案很小,也容易 git 版控,實現 Infrastructure as Code 理想。
  • 儘管只有一個 container,也建議使用 docker-compose.yml