建立我的第一個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 裡面應該會看到有兩個服務
