Sổ tay Docker: Cách sử dụng Docker Compose

Trong phần trước, bạn đã học cách chạy ứng dụng trên nhiều container trong Docker. Bạn cũng đã tìm hiểu về biến môi trường và volume được đặt tên trong Docker. Nếu bỏ lỡ thì bạn có thể xem lại bài viết này ở dưới đây:

Sổ tay Docker: Cách chạy ứng dụng JavaScript trên nhiều Container
Trong hướng dẫn này, bạn sẽ học cách triển khai ứng dụng trên nhiều container trong Docker.

Qua hướng dẫn đó, bạn đã biết được những khó khăn khi quản lý một dự án nhiều container. Thay vì viết nhiều lệnh như vậy, có một cách dễ dàng hơn để quản lý các dự án nhiều container, đó là sử dụng Docker Compose.

Theo tài liệu Docker -

Docker Compose là một công cụ để định nghĩa và chạy các ứng dụng trên nhiều Docker Container. Với Docker Compose, bạn sử dụng tệp YAML để cấu hình các dịch vụ của ứng dụng. Sau đó, với một lệnh duy nhất, bạn tạo và khởi động tất cả các dịch vụ từ cấu hình của mình.

Mặc dù Docker Compose hoạt động trong mọi môi trường, nhưng nó tập trung hơn vào việc phát triển và thử nghiệm. Việc sử dụng Docker Compose trên môi trường sản xuất (production) hoàn toàn không được khuyến khích.

Kiến thức cơ bản về Docker Compose

Truy cập thư mục mà bạn đã clone từ repository https://github.com/fhsinchy/docker-handbook-projects. Vào bên trong thư mục notes-api/api và tạo một tệp Dockerfile.dev. Đặt mã sau vào đó:

# stage one
FROM node:lts-alpine as builder

# install dependencies for node-gyp
RUN apk add --no-cache python make g++

WORKDIR /app

COPY ./package.json .
RUN npm install

# stage two
FROM node:lts-alpine

ENV NODE_ENV=development

USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app

COPY . .
COPY --from=builder /app/node_modules /home/node/app/node_modules

CMD [ "./node_modules/.bin/nodemon", "--config", "nodemon.json", "bin/www" ]

Mã gần như giống với mã Dockerfile mà bạn đã làm việc với trong phần trước. Ba điểm khác biệt trong tệp này như sau:

  • Trên dòng 10, chúng tôi chạy lệnh npm install thay vì npm run install --only=prod vì chúng tôi cũng muốn các phụ thuộc cho môi trường development.
  • Trên dòng 15, chúng tôi đặt biến môi trường NODE_ENV thành development thay vì production.
  • Ở dòng 24, chúng tôi sử dụng một công cụ có tên là nodemon để có được tính năng tải lại nóng cho API.

Bạn đã biết rằng dự án này có hai container:

  • notes-db - Một máy chủ cơ sở dữ liệu được cung cấp bởi PostgreSQL.
  • notes-api - REST API được cung cấp bởi Express.js.

Trong thế giới của Docker Compose, mỗi container tạo nên ứng dụng được gọi là một dịch vụ. Bước đầu tiên khi tạo một dự án nhiều container là định nghĩa các dịch vụ này.

Cũng giống như Docker daemon sử dụng Dockerfile để xây dựng image, Docker Compose sử dụng một tệp docker-compose.yaml để đọc các định nghĩa dịch vụ từ đó.

Đi tới thư mục notes-api và tạo một tệp docker-compose.yaml mới . Đặt mã sau vào tệp mới tạo:

version: "3.8"

services: 
    db:
        image: postgres:12
        container_name: notes-db-dev
        volumes: 
            - notes-db-dev-data:/var/lib/postgresql/data
        environment:
            POSTGRES_DB: notesdb
            POSTGRES_PASSWORD: secret
    api:
        build:
            context: ./api
            dockerfile: Dockerfile.dev
        image: notes-api:dev
        container_name: notes-api-dev
        environment: 
            DB_HOST: db ## same as the database service name
            DB_DATABASE: notesdb
            DB_PASSWORD: secret
        volumes: 
            - /home/node/app/node_modules
            - ./api:/home/node/app
        ports: 
            - 3000:3000

volumes:
    notes-db-dev-data:
        name: notes-db-dev-data

Mọi tệp docker-compose.yaml hợp lệ bắt đầu bằng cách chỉ định phiên bản tệp. Tại thời điểm viết bài, 3.8 là phiên bản mới nhất. Bạn có thể tra cứu phiên bản mới nhất tại đây.

Các khối trong tệp YAML được định nghĩa bằng cách thụt lề. Tôi sẽ đi qua từng khối và sẽ giải thích những gì chúng làm.

  • Khối services chứa các định nghĩa cho mỗi dịch vụ hoặc container trong ứng dụng. dbapi là hai dịch vụ trong dự án này.
  • Khối db định nghĩa một dịch vụ mới trong ứng dụng và lưu giữ thông tin cần thiết để khởi động container. Mọi dịch vụ đều yêu cầu image được tạo sẵn hoặc Dockerfile để chạy một container. Đối với dịch vụ db, chúng tôi đang sử dụng image PostgreSQL chính thức.
  • Không giống như dịch vụ db, image được tạo sẵn cho dịch vụ api không tồn tại. Vì vậy, chúng tôi sẽ sử dụng tệp Dockerfile.dev.
  • Khối volumes định nghĩa tên volume cần thiết cho các dịch vụ. Nó tạo volume notes-db-dev-data được sử dụng bởi dịch vụ db.

Bây giờ đã có tổng quan cấp cao về tệp docker-compose.yaml, chúng ta hãy xem xét kỹ hơn các dịch vụ riêng lẻ.

Mã định nghĩa cho dịch vụ db như sau:

db:
    image: postgres:12
    container_name: notes-db-dev
    volumes: 
        - db-data:/var/lib/postgresql/data
    environment:
        POSTGRES_DB: notesdb
        POSTGRES_PASSWORD: secret
  • Chỉ dẫn image chỉ định image và thẻ sử dụng cho container này. Chúng tôi đang sử dụng image postgres:12 để chạy container cơ sở dữ liệu.
  • Chỉ dẫn container_name chỉ định tên của container. Theo mặc định các container được đặt tên theo cú pháp <project directory name>_<service name>. Bạn có thể ghi đè điều đó bằng cách sử dụng container_name.
  • Chỉ dẫn volumes lưu giữ ánh xạ ổ đĩa cho các dịch vụ và hỗ trợ volume được đặt tên, volume ản danh và liên kết ràng buộc. Cú pháp <source>:<destination> giống với những gì bạn đã thấy trước đây.
  • Chỉ dẫn environment lưu giữ các giá trị của các biến môi trường khác nhau cần thiết cho dịch vụ.

Mã định nghĩa cho dịch vụ api như sau:

api:
    build:
        context: ./api
        dockerfile: Dockerfile.dev
    image: notes-api:dev
    container_name: notes-api-dev
    environment: 
        DB_HOST: db ## same as the database service name
        DB_DATABASE: notesdb
        DB_PASSWORD: secret
    volumes: 
        - /home/node/app/node_modules
        - ./api:/home/node/app
    ports: 
        - 3000:3000
  • Dịch vụ api không đi kèm với một image được xây dựng trước. Thay vào đó, nó có một cấu hình xây dựng. Dưới khối build, chúng ta xác định ngữ cảnh và tên của Dockerfile để xây dựng một khối. Bây giờ bạn đã hiểu về ngữ cảnh và Dockerfile nên tôi sẽ không dành thời gian giải thích những điều đó.
  • Chỉ dẫn image chỉ định tên của image được xây dựng. Nếu không được gán, image sẽ được đặt tên theo cú pháp <project directory name>_<service name>.
  • Bên trong chỉ dẫn environment, biến DB_HOST thể hiện một tính năng của Docker Compose. Nghĩa là, bạn có thể tham chiếu đến một dịch vụ khác trong cùng một ứng dụng bằng cách sử dụng tên của nó. Vì vậy, db ở đây, sẽ được thay thế bằng địa chỉ IP trong container dịch vụ api. Các biến DB_DATABASEDB_PASSWORD phải khớp với POSTGRES_DBPOSTGRES_PASSWORD tương ứng được định nghĩa ở dịch vụ db.
  • Trong khối volumes, bạn có thể thấy một volume ẩn danh và một liên kết ràng buộc được mô tả. Cú pháp giống với những gì bạn đã thấy trong các phần trước.
  • Chỉ dẫn ports xác định bất kỳ ánh xạ cổng. Cú pháp, <host port>:<container port> giống với tùy chọn --publish bạn đã sử dụng trước đây.

Cuối cùng, mã định nghĩa cho volumes như sau:

volumes:
    notes-db-dev-data:
        name: notes-db-dev-data

Bất kỳ volume được đặt tên nào được sử dụng trong bất kỳ dịch vụ nào phải được định nghĩa ở đây. Nếu bạn không xác định tên, tập sẽ được đặt tên theo cú pháp <project directory name>_<volume key> và tên ở đây là notes-db-dev-data.

Bạn có thể tìm hiểu về các tùy chọn khác nhau cho cấu hình volume trong tài liệu chính thức.

Cách chạy dịch vụ trong Docker Compose

Có một số cách chạy các dịch vụ được định nghĩa trong tệp YAML. Lệnh đầu tiên mà bạn sẽ học là lệnh up. Lệnh up xây dựng bất kỳ image nào bị thiếu, tạo container, và chạy tất cả chúng.

Tuy nhiên, trước khi thực hiện lệnh, hãy đảm bảo rằng bạn đã mở terminal của mình trong cùng một thư mục chứa tệp docker-compose.yaml. Điều này rất quan trọng đối với mọi lệnh docker-compose bạn thực hiện.

docker-compose --file docker-compose.yaml up --detach

# Creating network "notes-api_default" with the default driver
# Creating volume "notes-db-dev-data" with default driver
# Building api
# Sending build context to Docker daemon  37.38kB
#
# Step 1/13 : FROM node:lts-alpine as builder
#  ---> 471e8b4eb0b2
# Step 2/13 : RUN apk add --no-cache python make g++
#  ---> Running in 197056ec1964
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 197056ec1964
#  ---> 6609935fe50b
# Step 3/13 : WORKDIR /app
#  ---> Running in 17010f65c5e7
# Removing intermediate container 17010f65c5e7
#  ---> b10d12e676ad
# Step 4/13 : COPY ./package.json .
#  ---> 600d31d9362e
# Step 5/13 : RUN npm install
#  ---> Running in a14afc8c0743
### LONG INSTALLATION STUFF GOES HERE ###
#  Removing intermediate container a14afc8c0743
#  ---> 952d5d86e361
# Step 6/13 : FROM node:lts-alpine
#  ---> 471e8b4eb0b2
# Step 7/13 : ENV NODE_ENV=development
#  ---> Running in 0d5376a9e78a
# Removing intermediate container 0d5376a9e78a
#  ---> 910c081ce5f5
# Step 8/13 : USER node
#  ---> Running in cfaefceb1eff
# Removing intermediate container cfaefceb1eff
#  ---> 1480176a1058
# Step 9/13 : RUN mkdir -p /home/node/app
#  ---> Running in 3ae30e6fb8b8
# Removing intermediate container 3ae30e6fb8b8
#  ---> c391cee4b92c
# Step 10/13 : WORKDIR /home/node/app
#  ---> Running in 6aa27f6b50c1
# Removing intermediate container 6aa27f6b50c1
#  ---> 761a7435dbca
# Step 11/13 : COPY . .
#  ---> b5d5c5bdf3a6
# Step 12/13 : COPY --from=builder /app/node_modules /home/node/app/node_modules
#  ---> 9e1a19960420
# Step 13/13 : CMD [ "./node_modules/.bin/nodemon", "--config", "nodemon.json", "bin/www" ]
#  ---> Running in 5bdd62236994
# Removing intermediate container 5bdd62236994
#  ---> 548e178f1386
# Successfully built 548e178f1386
# Successfully tagged notes-api:dev
# Creating notes-api-dev ... done
# Creating notes-db-dev  ... done

Tùy chọn --detach hoặc -d ở đây có chức năng giống như bạn đã thấy trước đây. Tùy chọn --file hoặc -f chỉ cần thiết nếu file YAML không được đặt tên là docker-compose.yaml (nhưng tôi đã sử dụng ở đây cho mục đích minh họa).

Ngoài lệnh up còn có lệnh start. Sự khác biệt chính giữa hai điều này là lệnh start không tạo các container bị thiếu, chỉ chạy các container hiện có. Về cơ bản nó giống như lệnh container start.

Tùy chọn --build cho lệnh up bắt buộc xây dựng lại các image. Có một số tùy chọn khác cho lệnh up mà bạn có thể xem trong tài liệu chính thức.

Cách liệt kê các dịch vụ trong Docker Compose

Mặc dù các container dịch vụ được chạy bởi Docker Compose có thể được liệt kê bằng cách sử dụng lệnh container ls, nhưng có lệnh ps chỉ liệt kê các container được định nghĩa trong YAML.

docker-compose ps

#     Name                   Command               State           Ports         
# -------------------------------------------------------------------------------
# notes-api-dev   docker-entrypoint.sh ./nod ...   Up      0.0.0.0:3000->3000/tcp
# notes-db-dev    docker-entrypoint.sh postgres    Up      5432/tcp

Nó không nhiều thông tin như đầu ra của lệnh container ls, nhưng nó hữu ích khi bạn có hàng tấn container chạy đồng thời.

Cách thực thi các lệnh bên trong một dịch vụ đang chạy trong Docker Compose

Tôi hy vọng bạn nhớ ở phần trước rằng bạn phải chạy một số tập lệnh để tạo các bảng cơ sở dữ liệu cho API này.

Cũng giống như lệnh container exec, có một lệnh exec cho docker-compose. Cú pháp chung cho lệnh như sau:

docker-compose exec <service name> <command>

Để thực hiện lệnh npm run db:migrate bên trong dịch vụ api, bạn có thể thực hiện lệnh sau:

docker-compose exec api npm run db:migrate

# > notes-api@ db:migrate /home/node/app
# > knex migrate:latest
# 
# Using environment: development
# Batch 1 run: 1 migrations

Không giống như lệnh container exec, bạn không cần phải truyền cờ -it cho các phiên tương tác. docker-compose làm điều đó tự động.

Cách truy cập log từ một dịch vụ đang chạy trong Docker Compose

Bạn cũng có thể sử dụng lệnh logs để truy cập log từ một dịch vụ đang chạy. Cú pháp chung cho lệnh như sau:

docker-compose logs <service name>

Để truy cập nhật ký từ dịch vụ api, hãy thực hiện lệnh sau:

docker-compose logs api

# Attaching to notes-api-dev
# notes-api-dev | [nodemon] 2.0.7
# notes-api-dev | [nodemon] reading config ./nodemon.json
# notes-api-dev | [nodemon] to restart at any time, enter `rs`
# notes-api-dev | [nodemon] or send SIGHUP to 1 to restart
# notes-api-dev | [nodemon] ignoring: *.test.js
# notes-api-dev | [nodemon] watching path(s): *.*
# notes-api-dev | [nodemon] watching extensions: js,mjs,json
# notes-api-dev | [nodemon] starting `node bin/www`
# notes-api-dev | [nodemon] forking
# notes-api-dev | [nodemon] child pid: 19
# notes-api-dev | [nodemon] watching 18 files
# notes-api-dev | app running -> http://127.0.0.1:3000

Đây chỉ là một phần từ đầu ra log. Bạn có thể tham gia vào luồng đầu ra của dịch vụ và nhận log thời gian thực bằng cách sử dụng tùy chọn -f hoặc --follow. Bất kỳ log nào sau đó sẽ hiển thị ngay lập tức trong terminal miễn là bạn không thoát ra bằng cách nhấn ctrl + c hoặc đóng cửa sổ. Container sẽ tiếp tục chạy ngay cả khi bạn thoát ra khỏi cửa sổ log.

Cách dừng các dịch vụ trong Docker Compose

Để dừng dịch vụ, có hai cách tiếp cận mà bạn có thể thực hiện. Đầu tiên là lệnh down. Lệnh down dừng tất cả các container chạy và xóa chúng khỏi hệ thống. Nó cũng xóa bất kỳ mạng nào:

docker-compose down --volumes

# Stopping notes-api-dev ... done
# Stopping notes-db-dev  ... done
# Removing notes-api-dev ... done
# Removing notes-db-dev  ... done
# Removing network notes-api_default
# Removing volume notes-db-dev-data

Tùy chọn --volumes chỉ ra rằng bạn muốn xóa bất kỳ volume được đặt tên nào được định nghĩa trong khối volumes. Bạn có thể tìm hiểu về các tùy chọn bổ sung cho lệnh down trong tài liệu chính thức.

Một lệnh khác để dừng các dịch vụ là lệnh stop có chức năng giống với lệnh container stop. Nó dừng tất cả các container cho ứng dụng và giữ chúng. Những container này sau đó có thể được khởi động bằng lệnh start hoặc up.

Cách tạo một ứng dụng Full-stack trong Docker Compose

Trong phần phụ này, chúng tôi sẽ thêm giao diện người dùng cho Notes API của mình và biến nó thành một ứng dụng hoàn chỉnh. Tôi sẽ không giải thích bất kỳ tệp Dockerfile.dev nào trong phần này (ngoại trừ tệp cho dịch vụ nginx) vì chúng giống với một số tệp khác mà bạn đã thấy trong các phần trước.

Nếu bạn đã nhân bản kho lưu trữ mã dự án, thì hãy vào bên trong thư mục fullstack-notes-application. Mỗi thư mục bên trong thư mục gốc của dự án chứa mã cho từng dịch vụ và Dockerfile.‌

Trước khi chúng ta bắt đầu với tệp docker-compose.yaml, hãy xem một sơ đồ về cách thức ứng dụng sẽ làm việc:

Cách tạo một ứng dụng Full-stack trong Docker Compose

Thay vì chấp nhận các yêu cầu trực tiếp như chúng ta đã làm trước đây, trong ứng dụng này, tất cả các yêu cầu sẽ được nhận trước bởi một dịch vụ NGINX (hãy gọi nó là bộ định tuyến - router).

Sau đó, bộ định tuyến sẽ xem liệu điểm cuối được yêu cầu có thông tin /api ở trong đó hay không. Nếu có, router sẽ định tuyến yêu cầu đến back-end hoặc nếu không, router sẽ định tuyến yêu cầu đến front-end.

Bạn làm điều này vì khi bạn chạy một ứng dụng giao diện người dùng, nó không chạy bên trong container. Nó chạy trên trình duyệt, được phân phối từ một container. Do đó, mạng Compose không hoạt động như mong đợi và ứng dụng giao diện người dùng không tìm thấy dịch vụ api.

Mặt khác, NGINX chạy bên trong một container và có thể giao tiếp với các dịch vụ khác nhau trên toàn bộ ứng dụng.

Tôi sẽ không đi sâu vào cấu hình của NGINX ở đây. Chủ đề đó nằm ngoài phạm vi của hướng dẫn này. Nhưng nếu bạn muốn xem nó, hãy tiếp tục và kiểm tra các tệp /notes-api/nginx/development.conf/notes-api/nginx/production.conf. Mã cho /notes-api/nginx/Deockerfile.dev như sau:

FROM nginx:stable-alpine

COPY ./development.conf /etc/nginx/conf.d/default.conf

Tất cả những gì nó làm là sao chép tệp cấu hình vào /etc/nginx/conf.d/default.conf bên trong container.

Hãy cập nhật tệp docker-compose.yaml. Ngoài các dịch vụ apidb sẽ có các dịch vụ clientnginx. Cũng sẽ có một số định nghĩa về mạng mà tôi sẽ trình bày ngay sau đây.

version: "3.8"

services: 
    db:
        image: postgres:12
        container_name: notes-db-dev
        volumes: 
            - db-data:/var/lib/postgresql/data
        environment:
            POSTGRES_DB: notesdb
            POSTGRES_PASSWORD: secret
        networks:
            - backend
    api:
        build: 
            context: ./api
            dockerfile: Dockerfile.dev
        image: notes-api:dev
        container_name: notes-api-dev
        volumes: 
            - /home/node/app/node_modules
            - ./api:/home/node/app
        environment: 
            DB_HOST: db ## same as the database service name
            DB_PORT: 5432
            DB_USER: postgres
            DB_DATABASE: notesdb
            DB_PASSWORD: secret
        networks:
            - backend
    client:
        build:
            context: ./client
            dockerfile: Dockerfile.dev
        image: notes-client:dev
        container_name: notes-client-dev
        volumes: 
            - /home/node/app/node_modules
            - ./client:/home/node/app
        networks:
            - frontend
    nginx:
        build:
            context: ./nginx
            dockerfile: Dockerfile.dev
        image: notes-router:dev
        container_name: notes-router-dev
        restart: unless-stopped
        ports: 
            - 8080:80
        networks:
            - backend
            - frontend

volumes:
    db-data:
        name: notes-db-dev-data

networks: 
    frontend:
        name: fullstack-notes-application-network-frontend
        driver: bridge
    backend:
        name: fullstack-notes-application-network-backend
        driver: bridge

Tệp gần như giống với tệp trước đó bạn đã làm việc. Điều duy nhất cần giải thích là cấu hình mạng. Mã cho khối networks như sau:

networks: 
    frontend:
        name: fullstack-notes-application-network-frontend
        driver: bridge
    backend:
        name: fullstack-notes-application-network-backend
        driver: bridge

Tôi đã định nghĩa hai mạng cầu nối. Theo mặc định, Compose tạo một mạng cầu nối và gắn tất cả các container vào đó. Tuy nhiên, trong dự án này, tôi muốn cách ly mạng. Vì vậy, tôi đã định nghĩa hai mạng, một cho các dịch vụ front-end và một cho các dịch vụ back-end.

Tôi cũng đã thêm khối networks trong mỗi định nghĩa dịch vụ. Bằng cách này dịch vụ api và dịch vụ db sẽ được gắn vào một mạng và dịch vụ client sẽ được gắn vào một mạng riêng biệt. Nhưng dịch vụ nginx sẽ được gắn vào cả hai mạng để nó có thể hoạt động như một bộ định tuyến giữa các dịch vụ front-end và back-end.

Bắt đầu tất cả các dịch vụ bằng cách thực hiện lệnh sau:

docker-compose --file docker-compose.yaml up --detach

# Creating network "fullstack-notes-application-network-backend" with driver "bridge"
# Creating network "fullstack-notes-application-network-frontend" with driver "bridge"
# Creating volume "notes-db-dev-data" with default driver
# Building api
# Sending build context to Docker daemon  37.38kB
# 
# Step 1/13 : FROM node:lts-alpine as builder
#  ---> 471e8b4eb0b2
# Step 2/13 : RUN apk add --no-cache python make g++
#  ---> Running in 8a4485388fd3
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 8a4485388fd3
#  ---> 47fb1ab07cc0
# Step 3/13 : WORKDIR /app
#  ---> Running in bc76cc41f1da
# Removing intermediate container bc76cc41f1da
#  ---> 8c03fdb920f9
# Step 4/13 : COPY ./package.json .
#  ---> a1d5715db999
# Step 5/13 : RUN npm install
#  ---> Running in fabd33cc0986
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container fabd33cc0986
#  ---> e09913debbd1
# Step 6/13 : FROM node:lts-alpine
#  ---> 471e8b4eb0b2
# Step 7/13 : ENV NODE_ENV=development
#  ---> Using cache
#  ---> b7c12361b3e5
# Step 8/13 : USER node
#  ---> Using cache
#  ---> f5ac66ca07a4
# Step 9/13 : RUN mkdir -p /home/node/app
#  ---> Using cache
#  ---> 60094b9a6183
# Step 10/13 : WORKDIR /home/node/app
#  ---> Using cache
#  ---> 316a252e6e3e
# Step 11/13 : COPY . .
#  ---> Using cache
#  ---> 3a083622b753
# Step 12/13 : COPY --from=builder /app/node_modules /home/node/app/node_modules
#  ---> Using cache
#  ---> 707979b3371c
# Step 13/13 : CMD [ "./node_modules/.bin/nodemon", "--config", "nodemon.json", "bin/www" ]
#  ---> Using cache
#  ---> f2da08a5f59b
# Successfully built f2da08a5f59b
# Successfully tagged notes-api:dev
# Building client
# Sending build context to Docker daemon  43.01kB
# 
# Step 1/7 : FROM node:lts-alpine
#  ---> 471e8b4eb0b2
# Step 2/7 : USER node
#  ---> Using cache
#  ---> 4be5fb31f862
# Step 3/7 : RUN mkdir -p /home/node/app
#  ---> Using cache
#  ---> 1fefc7412723
# Step 4/7 : WORKDIR /home/node/app
#  ---> Using cache
#  ---> d1470d878aa7
# Step 5/7 : COPY ./package.json .
#  ---> Using cache
#  ---> bbcc49475077
# Step 6/7 : RUN npm install
#  ---> Using cache
#  ---> 860a4a2af447
# Step 7/7 : CMD [ "npm", "run", "serve" ]
#  ---> Using cache
#  ---> 11db51d5bee7
# Successfully built 11db51d5bee7
# Successfully tagged notes-client:dev
# Building nginx
# Sending build context to Docker daemon   5.12kB
# 
# Step 1/2 : FROM nginx:stable-alpine
#  ---> f2343e2e2507
# Step 2/2 : COPY ./development.conf /etc/nginx/conf.d/default.conf
#  ---> Using cache
#  ---> 02a55d005a98
# Successfully built 02a55d005a98
# Successfully tagged notes-router:dev
# Creating notes-client-dev ... done
# Creating notes-api-dev    ... done
# Creating notes-router-dev ... done
# Creating notes-db-dev     ... done

Bây giờ hãy truy cập http://localhost:8080 và xem kết quả.

Docker ComposerCách tạo một ứng dụng Full-stack trong Docker Compose

Hãy thử thêm và xóa các ghi chú để xem ứng dụng có hoạt động bình thường hay không. Dự án cũng đi kèm với các tập lệnh shell và Makefile. Khám phá chúng để xem cách bạn có thể chạy dự án này mà không cần sự trợ giúp của docker-compose như bạn đã làm trong phần trước.

Bài viết này được dịch từ cuốn sách The Docker Handbook của Farhan Hasin Chowdhury:

The Docker Handbook – 2021 Edition
The concept of containerization itself is pretty old. But the emergence of the Docker Engine [https://docs.docker.com/get-started/overview/#docker-engine] in2013 has made it much easier to containerize your applications. According to the Stack Overflow Developer Survey - 2020[https://insights.stackoverflow.com/survey/2020#overview…
DockerDevOps
Bài Viết Liên Quan:
Deploy ứng dụng ASP.NET Core bằng Docker
Trung Nguyen 29/03/2021
Deploy ứng dụng ASP.NET Core bằng Docker

Bài viết này sẽ hướng dẫn bạn chi tiết cách deploy ứng dụng ASP.NET Core bằng Docker.

Sổ tay Docker: Cách chạy ứng dụng JavaScript trên nhiều Container
Trung Nguyen 20/03/2021
Sổ tay Docker: Cách chạy ứng dụng JavaScript trên nhiều Container

Trong hướng dẫn này, bạn sẽ học cách triển khai ứng dụng trên nhiều container trong Docker.

Sổ tay Docker: Thao tác Docker Network cơ bản
Trung Nguyen 19/03/2021
Sổ tay Docker: Thao tác Docker Network cơ bản

Bạn sẽ tìm hiểu các kiến thức cơ bản về Docker Network, các thao tác mạng cơ bản như: tạo và xóa mạng, gắn container vào mạng, gỡ container khỏi mạng.

Sổ tay Docker: Cách xây dựng ứng dụng JavaScript với Docker
Trung Nguyen 19/03/2021
Sổ tay Docker: Cách xây dựng ứng dụng JavaScript với Docker

Trong hướng dẫn này, bạn sẽ học cách xây dựng ứng dụng JavaScript với Docker. Tạo image cho ứng dụng, tối ưu image, chạy container, ...