lesson-plan

🐳 DOCKER: NẮM VỮNG NỀN TẢNG


🎯 Mục Tiêu Tổng Quát


🎯 Mục Tiêu Chi Tiết (Bài học này)


1. 🌟 Giới Thiệu

Vấn đề “It works on my machine!”

Đây là một câu nói “kinh điển” trong giới lập trình, phản ánh một vấn đề phổ biến:

Hình ảnh minh họa: meme "works on my machine"

Giải pháp là gì? VMs vs Containers

Để hiểu rõ sự khác biệt giữa Máy ảo (Virtual Machines - VMs) và Containers, trước tiên chúng ta cần nắm được khái niệm Kernel.

Kernel là gì?

Máy ảo (VMs) hoạt động như thế nào?

Một máy ảo giống như một căn nhà riêng, có nền móng, điện nước riêng.

Containers (Docker) hoạt động như thế nào: “Chia sẻ Kernel của Host OS”

So sánh VMs và Containers

Tính năng Virtual Machines (VMs) Containers (Docker)
Isolation OS Level: Mỗi VM có một Hệ Điều Hành (Guest OS) và Kernel riêng biệt, hoàn toàn cô lập với Host OS và các VM khác. Process Level: Containers chia sẻ Kernel của Host OS. Cô lập ở mức process, filesystem, network.
Overhead Cao: Mỗi VM cần tài nguyên (RAM, CPU, Disk) cho cả Guest OS, gây lãng phí nếu chỉ chạy một ứng dụng nhỏ. Thấp: Chỉ tiêu tốn tài nguyên cho ứng dụng và các dependencies của nó, không cần Guest OS riêng.
Startup Time Chậm (phút): Phải khởi động cả một Guest OS. Nhanh (giây): Chỉ cần khởi động process của ứng dụng.
Portability Khá: Image VM thường rất lớn (GBs), di chuyển và quản lý phức tạp hơn. Rất cao: Image container nhỏ gọn hơn nhiều (MBs đến vài trăm MBs), dễ dàng di chuyển và chia sẻ.
Density Thấp: Số lượng VM có thể chạy trên một host bị giới hạn bởi tài nguyên cần cho Guest OS. Cao: Có thể chạy nhiều container hơn trên cùng một host do overhead thấp.
Use Case Cần chạy các OS khác nhau hoàn toàn trên cùng một host (VD: Windows trên Linux). Yêu cầu mức độ bảo mật kernel riêng biệt. Chạy các ứng dụng “legacy” không dễ containerize. Đóng gói và chạy ứng dụng, microservices, CI/CD pipelines, môi trường phát triển nhất quán.

Docker là một nền tảng containerization giúp đóng gói ứng dụng và tất cả các dependencies của nó (thư viện, runtime, system tools, code) thành một đơn vị chuẩn hóa, di động gọi là container. Container này có thể chạy nhất quán trên bất kỳ máy nào có cài Docker, bất kể môi trường bên dưới.

2. 🐧 Linux Cơ Bản Cho Docker

Tại sao cần biết Linux cơ bản?

Hiểu một số lệnh Linux cơ bản sẽ giúp bạn làm việc với Docker hiệu quả hơn rất nhiều.

Di chuyển & Quản lý file/thư mục

Quyền (Permissions) cơ bản

Khi dùng ls -l, bạn sẽ thấy thông tin quyền dạng drwxr-xr--:

Một số lệnh hữu ích khác

Trình quản lý gói (Package Managers)

Thường được sử dụng trong Dockerfile để cài đặt phần mềm.

3. 💡 Docker Core Concepts

Kiến trúc tổng quan của Docker

+----------------------+                          +------------------------------------------------------------+                         +-----------------------+
|                      | --- Lệnh (build, run) -->|                      DOCKER HOST                           |                         |                       |
|    DOCKER CLIENT     |                          |  +------------------------------------------------------+  |<---- Pull (kéo về) -----|       REGISTRY        |
|  (e.g., `docker` CLI)|                          |  |                  Docker Daemon                       |  |                         |  (e.g., Docker Hub,   |
|  (Bạn tương tác ở đây)|                         |  |                   (`dockerd`)                        |  |---- Push (đẩy lên) ---->|  AWS ECR, Google GCR) |
|                      | <-- Thông tin, kết quả --|  |        (Lắng nghe API, Quản lý Objects)              |  |                         |                       |
+----------------------+                          |  +-----------------------▲--┬---------------------------+  |                         +-----------------------+
                                                  |                          |  |                              |
                                                  | (Tải Images từ Registry  |  | (Chạy Containers từ Images)  |
                                                  |  Lưu trữ Images local)   |  | (Build Images từ Dockerfile) |
                                                  |                          |  |                              |
                                                  |  +-----------------------┴--▼---------------------------+  |
                                                  |  |       IMAGES            |       CONTAINERS           |  |
                                                  |  | (Templates Read-Only)   | (Running Instances)        |  |
                                                  |  |  - ubuntu:latest        |  - my_app_container        |  |
                                                  |  |  - nginx:alpine         |  - db_container            |  |
                                                  |  |  - my_custom_app:v1     |  - ...                     |  |
                                                  |  +-------------------------+----------------------------+  |
                                                  +------------------------------------------------------------+

Docker Engine

Docker Engine là thành phần cốt lõi của Docker, hoạt động theo kiến trúc client-server:

  +-----------------+      +-----------------+      +-------------------------+
  |   Người dùng    |----->|   Docker CLI    |----->|        REST API         |<---+ (Giao tiếp qua socket)
  +-----------------+      |   (`docker`)    |      +-------------------------+    |
                           +-----------------+                                     |
                                                      Docker Daemon (`dockerd`)    |
                                +--------------------------------------------------+
                                |                 "Trái tim của Docker" 🧠         |
                                |  - Chạy ngầm (background process) trên Host OS.  |
                                |  - Lắng nghe các yêu cầu từ Docker API.          |
                                |  - Quản lý các đối tượng Docker: Images,         |
                                |    Containers, Networks, Volumes.                |
                                |  - Tương tác với Kernel của Host OS để tạo       |
                                |    và quản lý sự cô lập của containers.          |
                                +--------------------------------------------------+

Image

Container

+-----------------------+         +-----------------------+
|     Image: my_app     |         |     Image: database   |
|  (Template Read-Only) |         |  (Template Read-Only) |
|  (Layers: OS, Runtime,|         |  (Layers: OS, DB Eng, |
|   Libs, App Code)     |         |   Config)             |
+-----------------------+         +-----------------------+
          |                                  |
          | .------------ `docker run` ------------. |
          V                                  V
+--------------------------------+  +--------------------------------+
|  Container A (my_app_instance1)|  | Container B (db_instance1)     |
|  (Image Layers + Writable Layer)|  | (Image Layers + Writable Layer)|
|  - Own Filesystem view         |  |  - Own Filesystem view         |
|  - Own Process space           |  |  - Own Process space           |
|  - Own Network Interface       |  |  - Own Network Interface       |
+--------------------------------+  +--------------------------------+

Trên cùng một Host Machine (chia sẻ Kernel của Host OS)

Dockerfile

Registry (Docker Hub)

4. ⚙️ Docker CLI Cơ Bản

Cú pháp chung: docker [OPTIONS] COMMAND [ARGUMENTS...] Để xem tất cả các lệnh: docker --help hoặc docker COMMAND --help (ví dụ docker run --help).

Quản lý Images

Quản lý Containers

Tương tác với Container

Xem thông tin và dọn dẹp

5. 📝 Dockerfile: Công Thức Tạo Image

Dockerfile là file text không có đuôi, mặc định tên là Dockerfile. Nó chứa các chỉ thị (instructions) để Docker tự động build một image.

Các chỉ thị (Instructions) phổ biến

Mỗi chỉ thị thường tạo một layer mới trong image.

  1. FROM <image>:<tag> hoặc FROM <image>@<digest>

    • Mục đích: Chỉ định base image (image nền) mà image của bạn sẽ được xây dựng dựa trên đó.
    • Lưu ý: Luôn là instruction đầu tiên trong Dockerfile (trừ trường hợp có ARG trước FROM).
    • Nên dùng tag cụ thể (VD: ubuntu:22.04) thay vì latest để đảm bảo tính tái lập (reproducibility). Dùng digest (sha256:...) cho độ tin cậy cao nhất.
    • Ví dụ: FROM ubuntu:22.04, FROM node:18-alpine, FROM mcr.microsoft.com/dotnet/sdk:6.0
  2. LABEL <key>=<value> [<key2>=<value2> ...]

    • Mục đích: Thêm metadata vào image dưới dạng cặp key-value (VD: maintainer, version, description).
    • Ví dụ: LABEL maintainer="[email protected]" version="1.0" description="My awesome app"
  3. ARG <name>[=<default_value>]

    • Mục đích: Định nghĩa biến chỉ tồn tại trong quá trình build image (docker build).
    • Giá trị có thể được truyền vào từ lệnh docker build --build-arg <name>=<value>.
    • Nếu ARG được khai báo trước FROM, nó có thể được dùng trong FROM.
    • Ví dụ: ARG APP_VERSION=1.0.0
    • Ví dụ:

      ARG NODE_VERSION=18
      FROM node:${NODE_VERSION}-alpine as builder
      
  4. ENV <key>=<value> hoặc ENV <key1>=<value1> <key2>=<value2> ...

    • Mục đích: Thiết lập biến môi trường. Biến này sẽ tồn tại cả trong quá trình build và khi container chạy từ image đó.
    • Giá trị có thể được ghi đè khi chạy container (docker run -e <key>=<new_value>).
    • Ví dụ: ENV NODE_ENV=production
    • Ví dụ:

      ENV APP_HOME=/usr/src/app \
          PATH=$APP_HOME/node_modules/.bin:$PATH
      
  5. WORKDIR /path/to/workdir

    • Mục đích: Thiết lập thư mục làm việc (working directory) cho các instruction tiếp theo như RUN, CMD, ENTRYPOINT, COPY, ADD.
    • Nếu thư mục không tồn tại, nó sẽ được tạo.
    • Nên dùng đường dẫn tuyệt đối.
    • Ví dụ: WORKDIR /app (các lệnh sau đó như COPY . . sẽ copy vào /app)
  6. COPY [--chown=<user>:<group>] <src_on_host>... <dest_in_image>

    • Mục đích: Sao chép file hoặc thư mục từ “build context” (thư mục chứa Dockerfile trên host) vào filesystem của image.
    • <src_on_host> phải là đường dẫn tương đối so với build context.
    • <dest_in_image> có thể là đường dẫn tuyệt đối, hoặc tương đối so với WORKDIR.
    • Không hỗ trợ lấy file từ URL hoặc giải nén tự động.
    • --chown: Thay đổi owner và group của file/thư mục được copy.
    • Ví dụ: COPY . . (sao chép toàn bộ nội dung thư mục build context vào WORKDIR trong image)
    • Ví dụ: COPY package.json yarn.lock ./
    • Ví dụ: COPY --chown=appuser:appgroup app.jar /opt/app/
  7. ADD [--chown=<user>:<group>] <src_on_host_or_URL>... <dest_in_image>

    • Mục đích: Tương tự COPY, nhưng có thêm một số “magic”:
      • Nếu <src> là URL, nó sẽ tải file về và copy vào <dest>.
      • Nếu <src> là một file nén tar (VD: .tar.gz, .tar.bz2), nó sẽ tự động giải nén vào <dest>.
    • Khuyến cáo: Ưu tiên dùng COPY trừ khi bạn thực sự cần tính năng tải URL hoặc tự động giải nén của ADD, vì COPY rõ ràng và dễ đoán hơn.
    • Ví dụ: ADD https://example.com/config.zip /app/config/ (sẽ tải và giải nén)
  8. RUN <command> (shell form) hoặc RUN ["executable", "param1", "param2"] (exec form)

    • Mục đích: Thực thi bất kỳ lệnh nào trong một layer mới của image, bên trên image hiện tại. Kết quả của lệnh sẽ được commit vào layer mới.
    • Thường dùng để cài đặt packages, dependencies, biên dịch code, tạo thư mục, thay đổi quyền,…
    • Shell form: RUN apt-get update && apt-get install -y nginx (chạy trong /bin/sh -c <command> hoặc shell được chỉ định bởi SHELL).
    • Exec form: RUN ["/bin/bash", "-c", "echo hello"] (không dùng shell, thực thi trực tiếp).
    • Để giảm số lượng layer, có thể nối nhiều lệnh bằng &&.
    • Ví dụ:

      RUN apt-get update && apt-get install -y --no-install-recommends \
              python3 python3-pip \
          && rm -rf /var/lib/apt/lists/*
      
    • Ví dụ: RUN npm install --production
  9. EXPOSE <port> [<port>/<protocol>...]

    • Mục đích: Thông báo cho Docker rằng container sẽ lắng nghe trên các network port được chỉ định khi chạy.
    • Đây chỉ là thông tin metadata, không tự động publish port ra host. Bạn vẫn cần dùng cờ -p hoặc -P với docker run để thực sự map port.
    • Protocol mặc định là tcp. Có thể chỉ định udp.
    • Ví dụ: EXPOSE 80 (ngầm hiểu là 80/tcp)
    • Ví dụ: EXPOSE 3000/tcp 5000/udp
  10. CMD ["executable","param1","param2"] (exec form - ưu tiên)

    • CMD command param1 param2 (shell form)
    • CMD ["param1","param2"] (làm tham số mặc định cho ENTRYPOINT)
    • Mục đích: Cung cấp lệnh mặc định và/hoặc tham số sẽ được thực thi khi container khởi động từ image này.
    • Lưu ý:
      • Chỉ có một CMD instruction có hiệu lực trong Dockerfile. Nếu có nhiều CMD, chỉ CMD cuối cùng sẽ được dùng.
      • Lệnh và tham số trong CMD có thể bị ghi đè hoàn toàn bởi command và arguments được cung cấp khi chạy docker run <image> [COMMAND_TO_OVERRIDE_CMD].
      • Exec form (["executable", ...]) là dạng được khuyến khích vì nó rõ ràng và không bị ảnh hưởng bởi shell.
    • Ví dụ (exec form): CMD ["nginx", "-g", "daemon off;"]
    • Ví dụ (shell form): CMD echo "Hello Docker"
    • Ví dụ (làm param cho ENTRYPOINT):

      ENTRYPOINT ["python", "app.py"]
      CMD ["--port", "8080"]
      
  11. ENTRYPOINT ["executable","param1","param2"] (exec form - ưu tiên)

    • ENTRYPOINT command param1 param2 (shell form)
    • Mục đích: Cấu hình container sẽ chạy như một executable. Lệnh này sẽ luôn được thực thi khi container khởi động.
    • Khác biệt với CMD:
      • Các tham số truyền vào docker run <image> [ARGS_FOR_ENTRYPOINT] sẽ được nối vào sau ENTRYPOINT (nếu là exec form).
      • ENTRYPOINT khó bị ghi đè hơn CMD. Để ghi đè ENTRYPOINT, cần dùng docker run --entrypoint <new_command>.
    • CMD có thể được dùng kết hợp với ENTRYPOINT để cung cấp các tham số mặc định cho ENTRYPOINT.
    • Ví dụ:

      ENTRYPOINT ["java", "-jar", "/app/my-app.jar"]
      CMD ["--profile=prod"] # Tham số mặc định
      # Khi chạy `docker run my-image --profile=dev`, container sẽ chạy:
      # java -jar /app/my-app.jar --profile=dev
      
    • Ví dụ (shell form, ít dùng hơn): ENTRYPOINT exec top -b (dùng exec để top là PID 1)
  12. VOLUME ["/path/to/volume"] hoặc VOLUME /path1 /path2 ...

    • Mục đích: Tạo một mount point với tên được chỉ định và đánh dấu nó là nơi chứa dữ liệu bền bỉ từ host hoặc từ một volume khác.
    • Khi container chạy, Docker sẽ tự động tạo một anonymous volume và mount vào đường dẫn này nếu không có volume nào khác được mount vào đó bằng -v hoặc --mount khi docker run.
    • Hữu ích để lưu trữ dữ liệu mà bạn không muốn bị mất khi container bị xóa (VD: database files, logs, user uploads).
    • Ví dụ: VOLUME /var/lib/mysql, VOLUME /app/data /app/logs
  13. USER <user>[:<group>] hoặc USER <UID>[:<GID>]

    • Mục đích: Thiết lập user name (hoặc UID) và tùy chọn group (hoặc GID) để chạy các lệnh RUN, CMD, ENTRYPOINT tiếp theo, cũng như user mặc định khi container chạy.
    • Best Practice: Chạy ứng dụng với user không phải root (non-root user) để tăng cường bảo mật. User này cần được tạo trước (VD: bằng RUN groupadd ... && useradd ...).
    • Ví dụ:

      RUN groupadd -r myapp && useradd --no-log-init -r -g myapp myappuser
      USER myappuser
      
  14. ONBUILD <INSTRUCTION>

    • Mục đích: Thêm một trigger instruction vào image. Instruction này sẽ không thực thi khi image hiện tại được build, mà sẽ thực thi khi một image khác sử dụng image này làm base image (FROM current_image).
    • Hữu ích cho việc tạo base images dùng chung mà cần thực hiện một số thao tác trên code của image con (VD: copy source code, chạy build script).
    • Ví dụ (trong một base image my-node-app-base):

      ONBUILD COPY . /app/src
      ONBUILD RUN npm install
      

      Khi một Dockerfile khác có FROM my-node-app-base, các lệnh ONBUILD này sẽ được thực thi.

  15. HEALTHCHECK [OPTIONS] CMD <command> hoặc HEALTHCHECK NONE

    • Mục đích: Chỉ định cách Docker kiểm tra xem container có còn “khỏe” (healthy) hay không.
    • Lệnh <command> sẽ được chạy bên trong container theo định kỳ. Nếu lệnh trả về exit code 0, container được coi là healthy. Exit code 1 là unhealthy.
    • Options: --interval=DURATION (mặc định 30s), --timeout=DURATION (mặc định 30s), --start-period=DURATION (mặc định 0s), --retries=N (mặc định 3).
    • HEALTHCHECK NONE: Tắt healthcheck được kế thừa từ base image.
    • Ví dụ:

      HEALTHCHECK --interval=5m --timeout=3s \
        CMD curl -f http://localhost/ || exit 1
      
  16. SHELL ["executable", "parameters"]

    • Mục đích: Thay đổi shell mặc định được sử dụng cho shell form của các lệnh RUN, CMD, ENTRYPOINT (mặc định là ["/bin/sh", "-c"] trên Linux, ["cmd", "/S", "/C"] trên Windows).
    • Ví dụ: SHELL ["/bin/bash", "-o", "pipefail", "-c"] (sử dụng bash và bật pipefail)

Ví dụ Dockerfile đơn giản (Node.js App)

Giả sử bạn có một ứng dụng Node.js đơn giản với cấu trúc:

my-node-app/
├── Dockerfile
├── package.json
├── server.js
└── ... (các file khác)

package.json:

{
  "name": "my-node-app",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "dependencies": {
    "express": "^4.17.1"
    // ... các dependencies khác
  }
}

Dockerfile:

# 1. Chọn base image: một phiên bản Node.js cụ thể, ưu tiên Alpine cho image nhỏ gọn
FROM node:18-alpine

# (Tùy chọn) Thêm labels để cung cấp metadata
LABEL maintainer="[email protected]"
LABEL version="1.0"
LABEL description="My Awesome Node.js App"

# 2. Tạo thư mục làm việc bên trong container
# Lệnh WORKDIR sẽ tạo thư mục nếu chưa tồn tại và cd vào đó
WORKDIR /usr/src/app

# 3. Sao chép file package.json (và package-lock.json nếu có) vào thư mục làm việc
# Copy những file này trước để tận dụng Docker layer caching.
# Nếu những file này không đổi, layer này sẽ được cache, tiết kiệm thời gian build `npm install`.
COPY package*.json ./

# 4. Cài đặt các dependencies của ứng dụng
# RUN npm ci --only=production  # 'npm ci' nhanh hơn và an toàn hơn cho build, dùng package-lock.json
                                # '--only=production' để bỏ qua devDependencies nếu là build cho production
RUN npm install
# Nếu không phải production build, có thể chỉ cần: RUN npm install

# 5. Sao chép toàn bộ source code của ứng dụng vào thư mục làm việc
# Copy sau khi npm install để nếu code thay đổi thì không cần chạy lại npm install (nếu package*.json không đổi)
COPY . .

# 6. (Tùy chọn) Thiết lập biến môi trường nếu cần
ENV NODE_ENV=production
ENV PORT=3000

# 7. Thông báo port mà ứng dụng sẽ chạy trên đó (metadata, không tự động publish)
EXPOSE ${PORT}
# Hoặc EXPOSE 3000 nếu không dùng biến PORT

# 8. Lệnh để chạy ứng dụng khi container khởi động
# Dùng exec form của CMD để tránh shell-isms và để process Node.js nhận tín hiệu (PID 1)
CMD [ "node", "server.js" ]
# Hoặc nếu dùng script trong package.json: CMD [ "npm", "start" ]

Để build image này:

cd /path/to/my-node-app
docker build -t my-node-app:1.0 .

Thứ tự lệnh và Caching

Docker build image theo từng lớp (layer), mỗi lệnh trong Dockerfile thường tạo ra một lớp. Docker sử dụng cơ chế caching rất thông minh:

Do đó, thứ tự các lệnh rất quan trọng để tối ưu hóa thời gian build:

Sử dụng .dockerignore file: Tương tự .gitignore, tạo file .dockerignore trong build context (cùng cấp với Dockerfile) để loại bỏ các file/thư mục không cần thiết ra khỏi build context trước khi nó được gửi tới Docker daemon. Điều này giúp:

node_modules
npm-debug.log
.git
.vscode
README.md
*.env

6. 🛠️ Thực Hành: Dockerize Ứng Dụng PHP “Hello World” với Apache

Mục tiêu: Tạo một Dockerfile để phục vụ một trang index.php đơn giản bằng web server Apache với PHP.

  1. Tạo thư mục dự án và file index.php: Mở terminal của bạn, tạo một thư mục mới (ví dụ php-hello-docker) và cd vào đó:

    mkdir php-hello-docker
    cd php-hello-docker
    

    Bên trong thư mục php-hello-docker, tạo file index.php với nội dung sau:

    <!-- index.php -->
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Hello PHP Docker!</title>
        <style>
          body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #e6e6fa; /* Lavender */
          }
          .container {
            text-align: center;
            padding: 20px;
            background-color: white;
            border-radius: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
          }
          h1 {
            color: #483d8b; /* DarkSlateBlue */
          }
          p {
            color: #555;
            font-size: 1.2em;
          }
          .php-info {
            margin-top: 15px;
            padding: 10px;
            background-color: #f0f0f0;
            border-left: 4px solid #483d8b;
          }
          img {
            margin-top: 20px;
            width: 100px;
          }
        </style>
      </head>
      <body>
        <div class="container">
          <h1>Hello from PHP inside Docker! 🐘</h1>
          <p>This is my first Dockerized PHP application.</p>
          <div class="php-info">
            <?php
              echo "PHP Version: " . phpversion();
            ?>
          </div>
          <img
            src="https://www.docker.com/wp-content/uploads/2022/03/Moby-logo.png"
            alt="Docker Logo"
          />
        </div>
      </body>
    </html>
    
  2. Tạo file Dockerfile: Trong cùng thư mục php-hello-docker, tạo file Dockerfile (không có phần mở rộng) với nội dung sau:

    # Bước 1: Sử dụng image PHP chính thức từ Docker Hub với Apache.
    # Ví dụ: php:8.3-apache (bạn có thể chọn phiên bản PHP khác nếu muốn)
    FROM php:8.3-apache
    
    # (Tùy chọn) Thêm thông tin về người tạo image
    LABEL maintainer="[email protected]"
    
    # Bước 2: Thiết lập thư mục làm việc (không bắt buộc cho ví dụ này vì Apache đã có mặc định)
    # WORKDIR /var/www/html
    # Image php:apache mặc định sử dụng /var/www/html làm DocumentRoot.
    
    # Bước 3: Sao chép file index.php tùy chỉnh của chúng ta từ build context
    # vào thư mục phục vụ web của Apache bên trong image.
    # '.' (dấu chấm đầu tiên) là thư mục hiện tại (build context) chứa index.php.
    # '/var/www/html/' là thư mục đích trong container.
    COPY ./index.php /var/www/html/index.php
    
    # Bước 4: Expose port 80 (Apache mặc định chạy và lắng nghe trên port 80 bên trong container)
    # Đây là metadata, không tự động publish port ra host.
    EXPOSE 80
    
    # Bước 5: Lệnh mặc định để Apache chạy đã được cấu hình trong base image php:apache.
    # Không cần thêm CMD trừ khi bạn muốn ghi đè hành vi mặc định.
    # CMD ["apache2-foreground"]
    
  3. Build Docker image: Đảm bảo bạn đang ở trong thư mục php-hello-docker (nơi chứa index.phpDockerfile). Chạy lệnh sau để build image:

    docker build -t my-first-php-app:1.0 .
    
    • docker build: Lệnh build image.
    • -t my-first-php-app:1.0: Tag image với tên my-first-php-app và phiên bản 1.0.
    • . : Chỉ định build context là thư mục hiện tại.

    Kiểm tra image đã được tạo:

    docker images
    

    Bạn sẽ thấy my-first-php-app với tag 1.0 trong danh sách.

  4. Chạy container từ image vừa build:

    docker run -d -p 8080:80 --name web_test_php my-first-php-app:1.0
    
    • -d: Chạy container ở chế độ detached (background).
    • -p 8080:80: Map port 8080 của máy host tới port 80 của container (port mà Apache đang lắng nghe).
    • --name web_test_php: Đặt tên cho container là web_test_php để dễ quản lý.
    • my-first-php-app:1.0: Tên image và tag để chạy.

    Kiểm tra container đang chạy:

    docker ps
    

    Bạn sẽ thấy container web_test_php đang chạy.

  5. Kiểm tra kết quả: Mở trình duyệt web (Chrome, Firefox,…) và truy cập địa chỉ http://localhost:8080. Bạn sẽ thấy trang “Hello from PHP inside Docker!” cùng với phiên bản PHP đang chạy và logo Docker.

  6. Xem logs của container (tùy chọn):

    docker logs web_test_php
    

    Bạn sẽ thấy logs access của Apache.

  7. Dọn dẹp: Sau khi hoàn thành, bạn có thể dừng và xóa container:

    docker stop web_test_php
    docker rm web_test_php
    

    Nếu muốn xóa cả image đã build:

    # docker rmi my-first-php-app:1.0
    

Chúc mừng! Bạn đã Dockerize thành công ứng dụng PHP đơn giản đầu tiên của mình.

Lưu ý thêm:

7. 🏋️ Bài Tập Nâng Cao: Dockerize Ứng Dụng PHP Động với Cấu Hình Môi Trường

Đề bài: Dockerize một ứng dụng PHP đơn giản có khả năng tùy chỉnh hiển thị thông qua biến môi trường và (tùy chọn) ghi lại số lượt truy cập

Mục tiêu học tập của bài tập này:


⬅️ Trở lại: PHP/Part6.md | 🏠 Home | ➡️ Tiếp theo: DEVOPS/Docker2.md