Ben Ben Blog.

建立我的第一個Blog - part 3: Strapi Docker and docker compose 環境建立

引言

建立這個Blog的時候有幾個主要目標。第一是想維持低成本,因為用cloud service長遠還是需要付費。第二是想自己試看看維護一台伺服器,並用CI/CD的方法來部署。

Strapi 官網也有Docker 部署的教學,但是有點錯誤的地方,並不能成功部署。

為什麼要用Docker

  • 一致性和可攜性

Docker可以很方便的用指令在不同電腦,不同os上快速部署服務,並可以讓環境統一。Docker 容器可以在任何支持 Docker 的環境中運行,這意味著開發、測試和生產環境中的應用程序行為一致,避免了「在我的機器上可以運行」的問題。例如,我很常在公司電腦,家裡電腦換來換去,在整合docker之後,我就可以一個命令就在新的電腦建立開發環境,此外,在deploy production 環境的時候,也可以統一伺服器的環境,避免dev與prod的環境有差異,導致難以察覺的bug。

  • 資源效率

因為我們不可能一個伺服器只用來運行一個服務,這時候就需要把不同的服務分隔開。相較於虛擬機,Docker 容器使用的系統資源更少,因為它們共享主機的操作系統內核,而不是每個容器都需要一個完整的操作系統。這樣可以提高性能並降低成本。

  • 快速啟動

Docker 容器的啟動時間通常在毫秒級別,相比之下,虛擬機需要幾分鐘才能啟動。這使得開發和測試過程更加靈活。降低我們重啓的

  • 簡化的應用包裝

Docker 將應用程序及其所有依賴項打包到一個容器中,使得部署變得簡單而一致。開發者只需關注代碼,而不必擔心環境配置問題。

  • 安全性

Docker 容器之間是相互隔離的,這意味著一個容器中的應用無法訪問其他容器的資源,提高了安全性

為什麼要用Docker Compose

一個Docker container 只可以運行一個服務,要同時管理多個服務就要用到Docker Compose。

  • 簡化多容器管理

Docker Compose 可以通過 YAML 文件定義多個服務及其依賴關係,這樣開發者可以使用單一命令啟動或停止整個應用堆棧,從而減少了手動管理多個容器的繁瑣。

  • 自動化和重複性

使用 Compose 定義的配置文件可以版本控制,這使得團隊成員能夠快速重現相同的環境,從而提高了協作效率

  • 網絡和存儲管理

Docker Compose 自動為服務創建網絡,確保它們能夠相互通信,同時也管理存儲卷,方便數據持久化

  • 方便 CI/CD 流程

Docker Compose 非常適合持續集成和持續部署(CI/CD),因為它能快速創建和摧毀測試環境,並且支持自動化測試流程

安裝Docker和Docker Compose

按照Docker官網指示下載就可以了 但如果是MacOS的話,強烈建議用orbstack

在安裝Docker ,Docker Compose也會一同安裝。

建立Docker file

在cms底下 建立Dockerfile

# Dockerfile
FROM node:20-alpine
# Installing libvips-dev for sharp Compatibility
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev git
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/app
COPY package.json yarn.lock ./
RUN yarn global add node-gyp
RUN yarn config set network-timeout 600000 -g && yarn install
COPY . .
RUN chown -R node:node /opt/app
USER node
RUN ["yarn", "build-type"]
RUN ["yarn", "build"]
EXPOSE 1337
CMD ["yarn", "develop"]

再建立一個Dockerfile.prod

# Dockerfile.prod
# Creating multi-stage build for production
FROM node:20-alpine AS build
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev git > /dev/null 2>&1
ENV NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/
COPY package.json yarn.lock ./
RUN yarn global add node-gyp
RUN yarn config set network-timeout 600000 -g && yarn install --production
ENV PATH=/opt/node_modules/.bin:$PATH
WORKDIR /opt/app
COPY . .
RUN ["yarn", "build-type"]
RUN yarn build

# Creating final production image
FROM node:20-alpine
RUN apk add --no-cache vips-dev
ENV NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY --from=build /opt/node_modules ./node_modules
WORKDIR /opt/app
COPY --from=build /opt/app ./
ENV PATH=/opt/node_modules/.bin:$PATH

RUN chown -R node:node /opt/app
USER node
EXPOSE 1337
CMD ["yarn", "start"]

建立Docker Compose 檔案

這邊會有三個檔案:

  • compose.yml (base compose file)
  • compose.prod.yml (for production env)
  • compose.dev.yml (for dev env)

在啓動dev環境的時候,會用到compose.yml和compose.dev.yml ,我們會用指令把兩個設定merge在一起,Prod同理。 其實就是override的概念,我們把prod和dev重覆的設定抽出來放到compose.yml,減少重覆性。 官網參考

檔案名稱是自己定而已,只是後面啓動的指令需要更改一下

# compose.yml
services:
  strapi:
    restart: unless-stopped
    volumes:
      - ./config:/opt/app/config
      - ./src:/opt/app/src
      - ./package.json:/opt/package.json
      - ./yarn.lock:/opt/yarn.lock
      - ./.env:/opt/app/.env
      - ./public/uploads:/opt/app/public/uploads
    depends_on:
      - strapiDB

  strapiDB:
    image: postgres
    restart: always
    # set shared memory limit when using docker-compose
    shm_size: 128mb
    volumes:
      - "./postgreSQL/init.sql:/docker-entrypoint-initdb.d/init.sql"
    # or set shared memory limit when deploy via swarm stack
    #  - type: tmpfs
    #    target: /dev/shm
    #    tmpfs:
    #      size: 134217728 # 128*2^20 bytes = 128Mb
    environment:
      POSTGRES_USER: ${DATABASE_USERNAME}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
      POSTGRES_DB: ${DATABASE_NAME}

compose.dev.yml

# compose.dev.yml
name: my-blog-cms-dev
services:
  strapi:
    container_name: strapi-dev
    build:
      context: .
      dockerfile: Dockerfile
    image: strapi-dev:latest
    ports:
      - "${PORT}:${PORT}"
    env_file: .env
    networks:
      - strapi-dev

  strapiDB:
    env_file:
      - .env.dev
    environment:
      PGPORT: "${DATABASE_PORT}"
    ports:
      - "${DATABASE_PORT}:${DATABASE_PORT}"
    networks:
      - strapi-dev
    volumes:
      - strapi-dev-data:/var/lib/postgresql/data

volumes:
  strapi-dev-data:

networks:
  strapi-dev:
    driver: bridge

compose.prod.yml

name: my-blog-cms
services:
  strapi:
    container_name: strapi
    build:
      context: .
      dockerfile: Dockerfile.prod
    image: strapi:latest
    ports:
      - "${PORT}:${PORT}"
    env_file: .env.prod
    networks:
      - strapi

  strapiDB:
    env_file:
      - .env.prod
    environment:
      PGPORT: "${DATABASE_PORT}"
    ports:
      - "${DATABASE_PORT}:${DATABASE_PORT}"
    networks:
      - strapi
    volumes:
      - strapi-data:/var/lib/postgresql/data

volumes:
  strapi-data:

networks:
  strapi:
    driver: bridge

啓動

運行dev環境:

docker compose --env-file .env.dev -f compose.yml -f compose.dev.yml up -d --build

運行production環境:

docker compose --env-file .env.prod -f compose.yml -f compose.prod.yml up -d --build

(optional)

為了方便我把上面的命令放到package.json的script裡面。

"compose:up:dev": "docker compose --env-file .env.dev -f compose.yml -f compose.dev.yml up -d --build",
"compose:up:prod": "docker compose --env-file .env.prod -f compose.yml -f compose.prod.yml up -d --build"

完成

打開localhost:1337 和 localhost:1338應該會能夠打開strapi畫面

在docker 裡面應該會看到有兩個服務 螢幕截圖 2024-11-14 下午5.55.16.png