Docker compose
Describe services in a compose.yaml, run them with Docker Compose, add services and use compose networks.
- series
- Docker Workshop
- episode
- ตอนที่ 11 จาก 17
- read
- อ่าน 8 นาที
Problem
การที่เราจะต้องมานั่นไล่พิมพ์คำสั่ง docker ไปทีละคำสั่งมันก็คงจะไม่ไหว ถ้าต้องมี container เยอะๆด้วยแล้ว เหนี่อยแย่เลย และอาจจะมีโอกาสพิมพ์ผิด setting ค่าผิดได้ง่ายๆเลยด้วย
Docker compose จะมาแก้ปัญหานี้แหละ
compose.yaml
docker compose จะให้เราเขียนรายระเอียดเกี่ยวกับ container ของเราไว้ในไฟล์เดียวเลย image อะไร network อะไร binding port อะไรบ้าง ใช้ volume อะไร ระบุไว้ในนี้ได้หมดเลย สุดท้ายเราจะรันคำสั่งแค่คำสั่งเดียวแล้วเราจะได้ระบบของเรามาแบบง่ายๆเลย
docker compose มี spec ที่เยอะมากๆ ใครสนใจอ่านก็ไปดูได้ที่ docker compose specs
แต่ในที่นี้เราจะมาใช้เท่าที่เราต้องใช้ ให้อธิบายทั้งหมดก็คงไม่ไหว และผมก็ไม่ได้รู้ทั้งหมดด้วย
มาเริ่มเขียน compose file กัน
ให้ตั้งชื่อไฟล์ว่า compose.yaml
services: next: build: context: . environment: NODE_ENV: production ports: - 3000:3000มาดูกันทีละส่วน
servicesต้องมีแน่ๆ บอกว่ามี container อะไรที่จะต้องรันบ้างnextอันนี้เป็นชื่อของ service จะใส่เป็นคำว่าอะไรก็ได้ แล้วแต่ชอบbuildส่วนนี้บางทีก็เห็นว่าใช้บ้างไม่ใช้บ้าง ถ้ามีbuildตัวนี้นั่นหมายความว่าเราจะไม่ใช้ docker image จากภายนอก เราจะสร้าง docker image ขึ้นมาใช้เอง มันคือส่วนที่บอก docker compose ว่าถ้าจะต้องสร้าง docker image ให้ไปมองหา Dockerfile ได้ที่ไหนcontext: .ซึ่งจุด.เป็นการบอกว่า Dockerfile ที่ต้องไปมองหามันอยู่ใน folder เดียวกันกับ compose file เลย
environmentคืออยากใส่ ENV อะไรลงไปตอน run container บ้าง ย้ำว่าตอน run นะไม่ใช้ตอนสร้าง imageportsก็บอกว่าอยากจะ binding port อะไรบ้าง เป็นตัวเลข ซ้าย คั่นด้วย colon แล้วก็ตัวเลขขวา ตัวเลขซ้ายคือ port บน host ตัวเลขขวาคือ port ใน container
run docker compose
ลองไป run docker compose กัน
ก่อนจะไปรันให้ kill container next ทิ้งไปก่อน
docker rm -f nextแล้วก็สั่ง
docker compose upตรงนี้ docker มันจะไปมองหาไฟล์ที่ชื่อ compose.yaml เอง ถ้าเจอมันก็จะเอามาใช้เลย แต่ถ้าเราตั้งเป็นชื่ออื่นก็ต้องระบุชื่อไฟล์ด้วยนะ
docker copmose -f <folder/filename> upก็จะได้ประมาณนี้
❯ docker compose up[+] Building 2.9s (22/22) FINISHED docker:orbstack => [next internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 2.25kB 0.0s => [next internal] load metadata for docker.io/library/n 2.3s => [next auth] library/node:pull token for registry-1.do 0.0s => [next internal] load .dockerignore 0.1s => => transferring context: 672B 0.0s => [next internal] load build context 0.1s => => transferring context: 967B 0.0s => [next base 1/1] FROM docker.io/library/node:18-alpine 0.0s => CACHED [next runner 1/8] WORKDIR /app 0.0s => CACHED [next runner 2/8] RUN addgroup --system --gid 0.0s => CACHED [next runner 3/8] RUN adduser --system --uid 1 0.0s => CACHED [next deps 1/4] RUN apk add --no-cache libc6-c 0.0s => CACHED [next deps 2/4] WORKDIR /app 0.0s => CACHED [next deps 3/4] COPY package.json yarn.lock* p 0.0s => CACHED [next deps 4/4] RUN if [ -f yarn.lock ]; the 0.0s => CACHED [next builder 2/4] COPY --from=deps /app/node_ 0.0s => CACHED [next builder 3/4] COPY . . 0.0s => CACHED [next builder 4/4] RUN if [ -f yarn.lock ]; 0.0s => CACHED [next runner 4/8] COPY --from=builder /app/pub 0.0s => CACHED [next runner 5/8] RUN mkdir .next 0.0s => CACHED [next runner 6/8] RUN chown nextjs:nodejs .nex 0.0s => CACHED [next runner 7/8] COPY --from=builder --chown= 0.0s => CACHED [next runner 8/8] COPY --from=builder --chown= 0.0s => [next] exporting to image 0.1s => => exporting layers 0.0s => => writing image sha256:d66ab9197c2675ae877ecdccf8cec 0.0s => => naming to docker.io/library/next-next 0.0s[+] Running 2/1 ✔ Network next_default Create... 0.1s ✔ Container next-next-1 Creat... 0.1sAttaching to next-1next-1 | ▲ Next.js 14.2.4next-1 | - Local: http://localhost:3000next-1 | - Network: http://0.0.0.0:3000next-1 |next-1 | ✓ Starting...next-1 | ✓ Ready in 37msจะเห็นว่า docker มันไปสร้าง image แล้วก็ Running ต่อเลย แล้ว terminal เราก็จะค้างไปเลย
ให้ลองเข้า http://localhost:3000 เพื่อดูว่า web ทำงานได้หรือเปล่า
Docker compose commands
แล้วให้กด Ctrl + c เพื่อออกมา เราจะใช้ terminal ได้ปกติ
แต่ว่า next container ของเราก็จะหยุดไปด้วย
แต่มันไม่ได้ถูกลบไปนะ ลองเช็คดูได้ด้วยคำสั่ง
docker container ls -aถ้าเราอยากจะให้มันกลับมา run อีกครั้ง ต่อจากที่หยุดไปก็สั่ง
docker compose startจะได้แบบนี้
❯ docker compose start[+] Running 1/1 ✔ Container next-next-1 Start... 0.3sจะเห็นว่า terminal เราก็ไม่ค้างแล้วด้วย
แต่ถ้าเราอยาก build ใหม่ก็ให้สั่ง stop ก่อน แล้วค่อสั่ง up อีกครั้งหนึ่ง
docker compose stopdocker compose up --buildใหม่อีกครั้งหนึ่งเท่านั้นเอง
ถ้าอยาก run compose up แล้ว terminal ไม่ค้างให้ใส่ detach ไปด้วยแบบนี้
docker compose up -ddocker compose up --build -dถ้าจะลบออกไปเลย ไม่ต้องเก็บ container ไว้แล้ว ให้สั่ง
docker compose downAdd other services to compose file
services: next: build: context: . environment: NODE_ENV: production ports: - 3000:3000 backend: image: hono environment: NODE_ENV: production ports: - 3333:3333backendเป็นชื่อจะใส่อะไรก็แล้วแต่ชอบimageเราจะไม่ build image เอง เราจะไปเอา image ที่มีอยู่แล้วมาใช้ ถ้ามีแล้วในเครื่องก็จะเอามาใช้เลย ถ้าไม่มีก็จะไป pull มา
ก่อนจะไปรัน ให้ไปลบ backend ตัวเก่าออกไปก่อน
docker rm -f honoแล้วสั่ง
docker compose up -d❯ docker compose up -d[+] Running 2/2 ✔ Container next-next-1 Ru... 0.0s ✔ Container next-backend-1 Started 0.3sเราสามารถ list รายการ container ที่รันอยู่โดยเอาเฉพาะ container ที่ระบุใน compose file มาได้ด้วยคำสั่งนี้
docker ps❯ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESf72294ef8bb2 next-next "docker-entrypoint.s…" 44 seconds ago Up 43 seconds 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp next-next-145eb8bb4181a hono "docker-entrypoint.s…" 44 seconds ago Up 5 seconds 0.0.0.0:3333->3333/tcp, :::3333->3333/tcp next-backend-1ตรงนี้ถ้าเปิด http://localhost:3000 จะเจอ error นั้นก็เพราะว่ามันหา http://hono:3333/users ไม่เจอ
มันหาไม่เจอก็เพราะว่า container ที่ชื่อ hono มันไม่มีแล้ว
docker compose มันจะเอาชื่อ service ไปตั้งเป็นชื่อ container ถ้าเราไม่ได้ระบุชื่อ container name ไว้
การจะแก้ตรงนี้ก็ทำได้ 2 วิธีคือ
- แก้ชื่อ service ให้เป็น hono ซะ
- ใส่ชื่อ container name ซะ
ซึ่งผมจะเลือกวิธีที่ 2 ละกัน
services: next: build: context: . environment: NODE_ENV: production ports: - 3000:3000 backend: container_name: hono image: hono environment: NODE_ENV: production ports: - 3333:3333 volumes: - ./uploads:/app/uploadsรอบนี้น่าจะเปิด website ได้แล้ว
compose networks
ใน compose file ถ้ามี services หลายๆตัวอยู่ด้วยกันแล้ว docker จะจับไว้ใน network วงเดียวกันอยู่แล้ว
ลองไปเช็คดูได้
docker network ls❯ docker network lsNETWORK ID NAME DRIVER SCOPE62490c6249ac bridge bridge localf995a98564a2 host host local22fb86b854db my_bridge1 bridge local344de646bf6e next_default bridge local13fa6f2ef770 none null localซึ่ง docker compose มันจะสร้าง network อีกวงนึงขึ้นมาน่ะแหละ
เวลาเราสั่ง docker compose down มันก็จะลบ network ออกไปให้เอง
แต่ถ้าเราอยากระบุชื่อ network เองก็ทำได้
networks: my_bridge2:
services: next: build: context: . environment: NODE_ENV: production ports: - 3001:3000 networks: - my_bridge2 backend: container_name: hono image: hono environment: NODE_ENV: production ports: - 3333:3333 volumes: - ./uploads:/app/uploads networks: - my_bridge2networksด้านบนคือบอกว่ามี network อะไรบ้างmy_bridge2ชื่อ network ที่เราต้องการจะสร้าง
ส่วนใน services ต่างๆ เราก็ต้องระบุด้วยว่าจะให้เชื่อมต่อไปที่ network ไหนบ้าง
พอสั่ง docker compose up -d มันก็จะสร้าง network my_bridge2 ให้เอง
use existing network in compose file
ถ้าเราอยากจะให้ compose file ของเรา เชื่อมต่อเข้ากับ networks ที่เคยถูกสร้างมาแล้ว ก็ให้เขียนแบบนี้
networks: my_bridge2: external: true name: my_bridge1
services: next: build: context: . environment: NODE_ENV: production ports: - 3001:3000 networks: - my_bridge2 backend: container_name: hono image: hono environment: NODE_ENV: production ports: - 3333:3333 volumes: - ./uploads:/app/uploads networks: - my_bridge2