Sitemap

以 Docker Compose 建立 Node.js 全端開發環境(六) — Image 發布

15 min readMar 31, 2023
Press enter or click to view image in full size

在上個章節我們成功在本地端打包 Image ,接下來我將會提供個簡易的方式來發布 Image。

而在 Registry 的選擇上相當多元,除了 docker 官方之外,在 GitHub、 GitLab 上也提供了各式各樣的 Registry 服務,當然你也可以自己架設私人的 Registry,而這章節我就是要在 GitHub 提供的 Registry 上發布 Image 。

上個章節結尾:

這個章節目標:

Github Action

因為在上個章節我們已經把 Image 打包的過程完成了,所以這個章節我們只要寫好 Github Action 的部分就好,首先我們先在專案目錄下新增 .github/workflows/proj6-publish-images.yml 這個檔案,在這個目錄下的任何 yml 檔案將會按照裡面所寫的規則在正確的時機點執行 Action:

#.github/workflows/proj6-publish-images.yml
name: Create and publish a Docker image

# 設定 Action 在 proj6 的 tag 被推到倉庫時執行
on:
push:
tags:
- "proj6*"

# 設定環境變數
env:
DOCKER_REGISTRY: ghcr.io
DOCKER_USERNAME: ${{ github.actor }}
DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

jobs:
# 加入一個用來打包與發佈 Image 的 Job
build-and-push-docker-image:
# 指定 runner 的系統環境
runs-on: ubuntu-latest

# 使用 strategy 產生兩個 job 個別打包 frontend 與 backend
strategy:
matrix:
docker-config:
- dockerfile: my-project-6/apps/backend/docker/Dockerfile
name: backend
context: my-project-6/apps/backend
- dockerfile: my-project-6/apps/frontend/docker/Dockerfile
name: frontend
context: my-project-6/apps/frontend

# 允許 github action 讀取倉庫內容,並允許寫入 packages
permissions:
contents: read
packages: write

# job 中逐個要執行的任務
steps:
# 簽出倉庫的程式碼
- name: Checkout repository
uses: actions/checkout@v3

# 登入 github 的 registry 以便等等可以 push 打包完的 image
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}

# 計算等等需要在發布時要用的 meta 資訊
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ github.repository }}-${{ matrix.docker-config.name }}

# build docker 並且發佈到指定的 registry
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: ${{ matrix.docker-config.context }}
file: ${{ matrix.docker-config.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

因為我這個專案的範例是一個個放在倉庫中,所以這邊是針對 proj6 這個 tag 來驅動,並且針對 my-project-6 目錄下的內容進行打包,所以要看這個檔案的話,可能要回到上個目錄下才找得到。

大部分都蠻容易理解的,這邊只針對幾個部分說明:

  1. 因為我們選擇的是 Github 的 Registry,所以採用 ghcr.io ,如果你想發布到其他的 Registry 的話,就可以直接修改我們在這邊設定的三個環境變數:DOCKER_REGISTRYDOCKER_USERNAMEDOCKER_PASSWORD。
  2. 因為打包與發佈 Image 的過程在 frontend 與 backend 是相同的,所以會使用 Strategy 的 Matrix 來產生兩個相同的 job,以官方的範例來簡單解釋下 Matrix 的部分:
Press enter or click to view image in full size
Github 官方說明截圖

我們逐步分解來看:

strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]

# =>
# 1. { fruit: apple, animal: cat }
# 2. { fruit: apple, animal: dog }
# 3. { fruit: pear, animal: cat }
# 4. { fruit: pear, animal: dog }

Matrix 的基本功能來說就是矩陣,目前兩個維度,各有兩個值,產生的 job 數量就是 2 x 2,得到四個 jobs。那再來 include 的功能呢?

include 將會獨立於原本的 Matrix 所設定的變數,他用來擴展 job 的內容,原始的矩陣值不能取代,若沒能找到能擴展的組合,他將會創建新的組合,接下來逐步的結果如下:

strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]
include:
- color: green

# =>
# 因為 color 不是原本 matrix 的 key 值,所以可以擴展每個組合

# 1. { fruit: apple, animal: cat, color: green }
# 2. { fruit: apple, animal: dog, color: green }
# 3. { fruit: pear, animal: cat, color: green }
# 4. { fruit: pear, animal: dog, color: green }

# ---------

strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]
include:
- color: green
- color: pink
animal: cat

# =>
# 首先, color 並不是原始的矩陣內容,所以可以被覆蓋,而上一個結果來看,
# 因為 animal 是 cat,所以我們抓出 1 和 3 來覆蓋。

# 1. { fruit: apple, animal: cat, color: pink }
# 2. { fruit: apple, animal: dog, color: green }
# 3. { fruit: pear, animal: cat, color: pink }
# 4. { fruit: pear, animal: dog, color: green }

# ---------

strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]
include:
- color: green
- color: pink
animal: cat
- fruit: apple
shape: circle

# =>
# 因為 shape 不是原本矩陣的內容所以可以擴展。
# 而因為 fruit 是 apple,所以我們抓出 1 和 2來擴展。

# 1. { fruit: apple, animal: cat, color: pink, shape: circle }
# 2. { fruit: apple, animal: dog, color: green, shape: circle }
# 3. { fruit: pear, animal: cat, color: pink }
# 4. { fruit: pear, animal: dog, color: green }

# ---------

strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]
include:
- color: green
- color: pink
animal: cat
- fruit: apple
shape: circle
- fruit: banana

# =>
# 因為 fruit 是原本矩陣的內容不能覆蓋,所以增添新的一個組合 5。

# 1. { fruit: apple, animal: cat, color: pink, shape: circle }
# 2. { fruit: apple, animal: dog, color: green, shape: circle }
# 3. { fruit: pear, animal: cat, color: pink }
# 4. { fruit: pear, animal: dog, color: green }
# 5. { fruit: banana }

# ---------

strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]
include:
- color: green
- color: pink
animal: cat
- fruit: apple
shape: circle
- fruit: banana
- fruit: banana
animal: cat

# =>
# 因為 fruit 和 animal 是原本矩陣的內容不能覆蓋,所以增添新的一個組合 6。

# 1. { fruit: apple, animal: cat, color: pink, shape: circle }
# 2. { fruit: apple, animal: dog, color: green, shape: circle }
# 3. { fruit: pear, animal: cat, color: pink }
# 4. { fruit: pear, animal: dog, color: green }
# 5. { fruit: banana }
# 6. {fruit: banana, animal: cat}

大概是這樣分解,其實這部分官方就有逐步說明了,只是也順便詳細的說明下而已,內容請參考:

按照上面的邏輯,我們現在可以知道,因為這邊使用 docker-config 提供了兩種組合,所以才會產生兩個 job 個別打包發布前端與後端的 Image。

3. 在最後一個 step 中,我們寫了這樣的內容:

- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: ${{ matrix.docker-config.context }}
file: ${{ matrix.docker-config.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

其實就跟我們使用 docker build 一樣,因為我們先前在 matrix 中指定好了變數,所以可以在這邊使用到,而這邊幾個參數:

  • context:指的是在哪個目錄進行 build
  • file:使用哪個 Dockerfile
  • push:如果為 true 的話就會推送到剛剛 login 的 Registry
  • tags 與 labels:這兩者則是在前面 docker/metadata-action@v4 產生好了。

4. 最後再稍微說明下,在上面有用到這三個變數:

  • github.actor
  • github.repository
  • secrets.GITHUB_TOKEN

分別是使用者的帳號、倉庫名稱、和用於驗證身份的 Token ,而這些變數都是 Github Action 自帶的,並不是需要自行設定的,而因為這此使用到的 Registry 就是 Github 本身的 Registry,自然使用到你自己的帳號就能登入。

我認為這算是使用 Github 與 Gitlab 提供的 Registry 方便的地方,你不需要產生多餘的一些 Token 或 Key ,對於想嘗試看看的新手,算是挺好入門。

發布 Image

如果以上的部分都完成了,當推送 proj6 這個 Tag 上去時,就會開始進行 Action 並最終打包成以下的結果,並且都是 proj6 的 tag:

Press enter or click to view image in full size

而與前幾個章節類似,只是這次我們把 Image 都改為剛剛我們發布的 Image ,在目錄下新增個docker-compose.prod.yml

# docker-compose.prod.yml
version: '3'

volumes:
postgres-store:
services:
postgres:
image: postgres
container_name: prod-my-project-postgres
environment:
- POSTGRES_DB=$DATABASE_NAME
- POSTGRES_USER=$DATABASE_USERNAME
- POSTGRES_PASSWORD=$DATABASE_PASSWORD
volumes:
- postgres-store:/var/lib/postgresql/data
backend:
image: ghcr.io/tokileecy/blog-post-docker-compose-nodejs-backend:proj6
user: 1000:1000
container_name: 'prod-my-project-backend'
restart: unless-stopped
environment:
- NODE_OPTIONS=--max_old_space_size=2048
- ALLOW_CORS_ORIGIN=$ALLOW_CORS_ORIGIN
- DATABASE_URL=postgresql://$DATABASE_USERNAME:$DATABASE_PASSWORD@postgres:5432/$DATABASE_NAME?connect_timeout=300
ports:
- 8081:8081
depends_on:
- postgres
frontend:
image: ghcr.io/tokileecy/blog-post-docker-compose-nodejs-frontend:proj6
container_name: 'prod-my-project-frontend'
restart: unless-stopped
environment:
- NODE_OPTIONS=--max_old_space_size=2048
- REACT_APP_PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
ports:
- 3000:80

如果想確認結果,就對剛新增好的 prod執行 docker compose -f docker-compose.prod.yml up 就會從 ghcr.io pull 下你發布的 Image 並執行起來了。

總結

這個章節簡單的介紹了如何發布 Image 到 Github 上的 packages,其實主要的內容反而是在說明 Matrix 那部分,由於 Github Action 的工具非常多,基本上想要的內容在 Marketplace 都能找到,也蠻多官方的 Action,只要知道如何使用這些 Action 很多事情都能輕鬆完成。

--

--

Toki Lee
Toki Lee

Written by Toki Lee

沒有技術上不可行,只是時間上做不到⋯

No responses yet