Dockerfile Là Gì? Cách Viết Dockerfile Chuẩn Chỉnh Từ A Đến Z

Dockerfile là trái tim của việc container hóa ứng dụng bằng Docker. Nếu bạn là một lập trình viên, DevOps engineer, hoặc đơn giản là tò mò về công nghệ container, việc nắm vững cách viết Dockerfile là một kỹ năng vô cùng quan trọng. Bài viết này sẽ giải đáp chi tiết Dockerfile là gì và hướng dẫn bạn cách viết Dockerfile một cách chuẩn chỉnh, dễ hiểu, ngay cả khi bạn là người mới bắt đầu.

Dockerfile Là Gì?

Dockerfile là một tập tin văn bản chứa các hướng dẫn (instructions) mà Docker sẽ sử dụng để tự động xây dựng (build) một image. Image này sau đó được dùng để tạo ra các container. Hãy tưởng tượng Dockerfile như một công thức nấu ăn, trong đó mỗi dòng lệnh là một bước để tạo ra một món ăn (container) hoàn chỉnh. Nói cách khác, Dockerfile định nghĩa môi trường và các bước cần thiết để ứng dụng của bạn chạy một cách nhất quán trên mọi nền tảng hỗ trợ Docker.

Tại Sao Cần Dockerfile?

  • Tính nhất quán: Đảm bảo ứng dụng chạy giống nhau trên mọi môi trường, từ máy tính cá nhân đến server production.
  • Khả năng tái tạo: Dễ dàng tạo lại môi trường ứng dụng khi cần thiết.
  • Tự động hóa: Quá trình build image được tự động hóa hoàn toàn.
  • Kiểm soát phiên bản: Dockerfile có thể được quản lý bằng hệ thống kiểm soát phiên bản (ví dụ: Git), giúp theo dõi thay đổi và phục hồi khi cần.
  • Dễ dàng chia sẻ: Dockerfile có thể được chia sẻ với đồng nghiệp hoặc cộng đồng, giúp tái sử dụng và cộng tác dễ dàng hơn.
  • Triển khai dễ dàng: Docker image tạo ra từ Dockerfile có thể triển khai trên bất kỳ nền tảng nào hỗ trợ Docker.

Cấu Trúc Cơ Bản Của Dockerfile

Dockerfile bao gồm một chuỗi các hướng dẫn (instructions). Mỗi hướng dẫn được viết trên một dòng riêng biệt và thường bắt đầu bằng chữ in hoa. Docker sẽ thực hiện các hướng dẫn này theo thứ tự từ trên xuống dưới để tạo ra image. Dưới đây là một số hướng dẫn quan trọng và thường dùng nhất:

  • FROM: Chỉ định image cơ sở (base image) mà bạn muốn xây dựng image của mình dựa trên đó. Ví dụ: FROM ubuntu:latest
  • MAINTAINER: (Không còn khuyến khích sử dụng, thay bằng LABEL) Thông tin về người bảo trì image.
  • LABEL: Gán metadata cho image. Ví dụ: LABEL version="1.0" description="My Awesome App"
  • RUN: Thực thi một lệnh trong container trong quá trình build image. Ví dụ: RUN apt-get update && apt-get install -y nginx
  • COPY: Sao chép tập tin hoặc thư mục từ máy host vào container. Ví dụ: COPY ./app /var/www/html
  • ADD: Tương tự như COPY, nhưng có thêm một số tính năng khác như tự động giải nén file nén. Thường được khuyến khích sử dụng COPY thay vì ADD.
  • WORKDIR: Thiết lập thư mục làm việc mặc định trong container. Ví dụ: WORKDIR /var/www/html
  • EXPOSE: Khai báo cổng mà ứng dụng sẽ lắng nghe. Ví dụ: EXPOSE 80
  • ENV: Thiết lập biến môi trường. Ví dụ: ENV APP_HOME /var/www/html
  • CMD: Chỉ định lệnh sẽ chạy khi container được khởi động. Chỉ có một CMD được thực thi, nếu có nhiều hơn, CMD cuối cùng sẽ được dùng. Ví dụ: CMD ["nginx", "-g", "daemon off;"]
  • ENTRYPOINT: Tương tự như CMD, nhưng khó bị ghi đè hơn. Thường được sử dụng để tạo ra các container có thể thực thi như một lệnh. Ví dụ: ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
  • USER: Chỉ định người dùng mà các lệnh tiếp theo sẽ được thực thi dưới quyền.
  • VOLUME: Tạo một mount point để truy cập dữ liệu từ bên ngoài container.
  • ARG: Định nghĩa một biến build-time, có thể được truyền vào khi build image.
  • ONBUILD: Xác định các lệnh sẽ được thực thi khi image này được sử dụng làm base image cho một image khác.
  • STOPSIGNAL: Chỉ định tín hiệu sẽ được sử dụng để dừng container.
  • HEALTHCHECK: Thiết lập một lệnh để kiểm tra sức khỏe của container.
  • SHELL: Cho phép thay đổi shell mặc định được sử dụng cho các lệnh RUN, CMD và ENTRYPOINT.

Hướng Dẫn Từng Bước Viết Dockerfile

Dưới đây là hướng dẫn chi tiết từng bước cách viết một Dockerfile hoàn chỉnh:

Bước 1: Xác định ứng dụng và môi trường cần thiết

Trước khi bắt tay vào viết Dockerfile, bạn cần xác định rõ ứng dụng của mình là gì, nó cần những gì để chạy, và bạn muốn môi trường container trông như thế nào.

Ví dụ: Ứng dụng của bạn là một ứng dụng web viết bằng Python sử dụng framework Flask, cần các thư viện như requests, gunicorn, và cần chạy trên Ubuntu.

Bước 2: Chọn Base Image phù hợp

Base image là nền tảng mà bạn sẽ xây dựng image của mình dựa trên đó. Việc chọn base image phù hợp rất quan trọng vì nó ảnh hưởng đến kích thước image, tính bảo mật và hiệu năng của ứng dụng.

  • Ưu tiên image chính thức: Sử dụng các image chính thức từ Docker Hub, được cung cấp và bảo trì bởi các nhà phát triển dự án (ví dụ: python:3.9-slim-buster thay vì một image không rõ nguồn gốc).
  • Chọn image tối giản: Sử dụng các image “slim” hoặc “alpine” để giảm kích thước image.
  • Đảm bảo tương thích: Chọn image tương thích với ngôn ngữ lập trình và các thư viện mà ứng dụng của bạn sử dụng.

Trong ví dụ này, bạn có thể chọn python:3.9-slim-buster làm base image.

Bước 3: Tạo Dockerfile

Tạo một file mới tên là Dockerfile (không có phần mở rộng) trong thư mục gốc của dự án.

Bước 4: Viết Dockerfile

Bắt đầu viết Dockerfile bằng cách khai báo base image:

FROM python:3.9-slim-buster

Bước 5: Cài đặt các phụ thuộc

Sử dụng lệnh RUN để cài đặt các phụ thuộc cần thiết cho ứng dụng. Nên sử dụng apt-get update trước khi cài đặt các gói để đảm bảo bạn đang sử dụng phiên bản mới nhất. Bạn nên kết hợp nhiều lệnh apt-get vào một dòng duy nhất để giảm số lượng layers trong image, giúp giảm kích thước image.

RUN apt-get update && 
    apt-get install -y --no-install-recommends 
    build-essential libpq-dev && 
    rm -rf /var/lib/apt/lists/*

Trong ví dụ này, bạn cài đặt build-essentiallibpq-dev vì có thể bạn cần chúng để cài đặt một số thư viện Python. --no-install-recommends giúp giảm kích thước image bằng cách không cài đặt các gói được “khuyến nghị” mà không thực sự cần thiết. rm -rf /var/lib/apt/lists/* xóa cache của apt để giảm kích thước image.

Bước 6: Sao chép source code

Sử dụng lệnh COPY để sao chép source code của ứng dụng vào container.

COPY . /app

Lệnh này sẽ sao chép tất cả các file và thư mục trong thư mục hiện tại (thư mục gốc của dự án) vào thư mục /app trong container.

Bước 7: Cài đặt các thư viện Python

Sử dụng pip để cài đặt các thư viện Python cần thiết. Nên sử dụng requirements.txt để quản lý các thư viện này.

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

WORKDIR /app thiết lập thư mục làm việc mặc định là /app. COPY requirements.txt . sao chép file requirements.txt vào thư mục /app. RUN pip install --no-cache-dir -r requirements.txt cài đặt các thư viện được liệt kê trong requirements.txt. --no-cache-dir giúp giảm kích thước image bằng cách không lưu cache của pip.

Bước 8: Khai báo cổng

Sử dụng lệnh EXPOSE để khai báo cổng mà ứng dụng sẽ lắng nghe.

EXPOSE 5000

Trong ví dụ này, ứng dụng Flask sẽ lắng nghe trên cổng 5000.

Bước 9: Thiết lập biến môi trường (nếu cần)

Sử dụng lệnh ENV để thiết lập các biến môi trường cần thiết.

ENV FLASK_APP app.py
ENV FLASK_ENV development

Bước 10: Chỉ định lệnh chạy ứng dụng

Sử dụng lệnh CMD để chỉ định lệnh sẽ chạy khi container được khởi động.

CMD ["flask", "run", "--host=0.0.0.0"]

Trong ví dụ này, lệnh flask run --host=0.0.0.0 sẽ khởi động ứng dụng Flask. --host=0.0.0.0 cho phép ứng dụng lắng nghe trên tất cả các interface, giúp bạn truy cập ứng dụng từ bên ngoài container.

Dockerfile hoàn chỉnh:

FROM python:3.9-slim-buster

RUN apt-get update && 
    apt-get install -y --no-install-recommends 
    build-essential libpq-dev && 
    rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

ENV FLASK_APP app.py
ENV FLASK_ENV development

CMD ["flask", "run", "--host=0.0.0.0"]

Bước 11: Build Image

Sử dụng lệnh docker build để build image từ Dockerfile.

docker build -t my-flask-app .

-t my-flask-app đặt tên cho image là my-flask-app. . chỉ định thư mục chứa Dockerfile (trong trường hợp này là thư mục hiện tại).

Bước 12: Chạy Container

Sử dụng lệnh docker run để chạy container từ image vừa build.

docker run -p 5000:5000 my-flask-app

-p 5000:5000 map cổng 5000 trên máy host vào cổng 5000 trong container.

Các Phương Pháp Tối Ưu Dockerfile

Để tạo ra các image nhỏ gọn, bảo mật và hiệu quả, bạn nên áp dụng các phương pháp tối ưu sau:

  • Sử dụng Multi-Stage Builds: Cho phép bạn sử dụng nhiều base image trong cùng một Dockerfile. Bạn có thể sử dụng một image lớn để build ứng dụng, sau đó copy các artifact cần thiết sang một image nhỏ hơn để chạy ứng dụng. Điều này giúp giảm kích thước image cuối cùng.

    # Stage 1: Build the application
    FROM maven:3.8.1-openjdk-17 AS builder
    WORKDIR /app
    COPY pom.xml .
    RUN mvn dependency:go-offline
    COPY src ./src
    RUN mvn clean install
    
    # Stage 2: Create the final image
    FROM openjdk:17-jre-slim
    WORKDIR /app
    COPY --from=builder /app/target/*.jar app.jar
    EXPOSE 8080
    ENTRYPOINT ["java", "-jar", "app.jar"]

    Trong ví dụ này, stage đầu tiên sử dụng maven:3.8.1-openjdk-17 để build ứng dụng Java. Stage thứ hai sử dụng openjdk:17-jre-slim, một image nhỏ hơn, và copy chỉ file JAR cần thiết từ stage đầu tiên.

  • Sử dụng .dockerignore: Tạo một file .dockerignore để loại trừ các file và thư mục không cần thiết khỏi quá trình build image. Điều này giúp giảm kích thước image và tăng tốc độ build.

    .git
    node_modules
    logs
    *.log
  • Sắp xếp các lệnh theo thứ tự: Sắp xếp các lệnh ít thay đổi lên trước, các lệnh thường xuyên thay đổi xuống sau. Docker sử dụng cache để tăng tốc độ build. Nếu một lệnh thay đổi, tất cả các lệnh sau nó sẽ phải được build lại.

  • Kết hợp các lệnh RUN: Kết hợp nhiều lệnh RUN vào một dòng duy nhất để giảm số lượng layers trong image.

  • Sử dụng copy instead of add: COPY minh bạch và dễ đoán hơn ADD, nó chỉ đơn giản là sao chép file và thư mục. ADD có thể tự động giải nén file nén hoặc tải file từ URL, nhưng điều này có thể gây ra các vấn đề không mong muốn.

  • Sử dụng HEALTHCHECK: Thêm một healthcheck để Docker có thể tự động restart container nếu nó không khỏe mạnh.

    HEALTHCHECK --interval=5m --timeout=3s 
      CMD curl -f http://localhost:8080/ || exit 1

    Trong ví dụ này, healthcheck sẽ kiểm tra xem ứng dụng web có trả về HTTP 200 OK hay không.

  • Quản lý bí mật: Tránh lưu trữ các bí mật (ví dụ: mật khẩu, API key) trong Dockerfile. Sử dụng Docker Secrets hoặc các giải pháp quản lý bí mật khác.

Trích dẫn từ anh Nguyễn Văn An, một DevOps Engineer với hơn 5 năm kinh nghiệm:

“Dockerfile là nền tảng để xây dựng các ứng dụng có khả năng mở rộng và dễ dàng quản lý. Việc tối ưu Dockerfile không chỉ giúp giảm kích thước image mà còn tăng tính bảo mật và hiệu năng của ứng dụng.”

Ví Dụ Về Các Dockerfile Phổ Biến

  • Dockerfile cho ứng dụng Node.js:

    FROM node:16-alpine
    
    WORKDIR /app
    COPY package*.json ./
    RUN npm install
    COPY . .
    EXPOSE 3000
    CMD ["npm", "start"]
  • Dockerfile cho ứng dụng Java (Sử dụng Spring Boot):

    FROM openjdk:17-jre-slim
    
    ARG JAR_FILE=target/*.jar
    COPY ${JAR_FILE} app.jar
    
    EXPOSE 8080
    ENTRYPOINT ["java", "-jar", "app.jar"]
  • Dockerfile cho ứng dụng Python (Sử dụng Django):

    FROM python:3.9-slim-buster
    
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    COPY . .
    
    EXPOSE 8000
    CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Anh Trần Minh Đức, một Software Architect với kinh nghiệm xây dựng hệ thống lớn, chia sẻ:

“Khi xây dựng các hệ thống microservices, Dockerfile đóng vai trò then chốt trong việc đảm bảo tính độc lập và khả năng mở rộng của từng service. Việc nắm vững Dockerfile giúp kiến trúc sư hệ thống có thể thiết kế và triển khai các hệ thống phức tạp một cách dễ dàng hơn.”

Các Lỗi Thường Gặp Khi Viết Dockerfile và Cách Khắc Phục

  • Image quá lớn: Kiểm tra xem bạn có cài đặt các gói không cần thiết hay không. Sử dụng multi-stage builds để giảm kích thước image.
  • Lỗi cache: Sắp xếp lại các lệnh trong Dockerfile để tận dụng cache hiệu quả hơn.
  • Lỗi permission: Đảm bảo rằng các lệnh RUN được thực thi dưới quyền người dùng phù hợp. Bạn có thể sử dụng lệnh USER để thay đổi người dùng. Nếu bạn gặp lỗi docker bị lỗi permission denied, hãy kiểm tra lại quyền truy cập của người dùng trong container.
  • Ứng dụng không chạy: Kiểm tra xem bạn có expose đúng cổng hay không. Đảm bảo rằng lệnh CMD chạy đúng ứng dụng của bạn.
  • Lỗi khi cài đặt dependencies: Kiểm tra xem bạn có cài đặt tất cả các dependencies cần thiết hay không. Đảm bảo rằng bạn đang sử dụng phiên bản chính xác của các dependencies.

Kết Luận

Dockerfile là một công cụ mạnh mẽ giúp bạn container hóa ứng dụng một cách dễ dàng và hiệu quả. Bằng cách nắm vững các hướng dẫn cơ bản, áp dụng các phương pháp tối ưu và tránh các lỗi thường gặp, bạn có thể tạo ra các image nhỏ gọn, bảo mật và hiệu quả, giúp ứng dụng của bạn chạy một cách nhất quán trên mọi môi trường. Hy vọng qua bài viết này, bạn đã hiểu rõ hơn về Dockerfile là gì và có thể tự tin viết Dockerfile cho các ứng dụng của mình. Để tìm hiểu thêm về các công nghệ container khác, bạn có thể tham khảo bài viết sự khác nhau giữa docker và podman.

FAQ – Các Câu Hỏi Thường Gặp Về Dockerfile

  1. Dockerfile có phải là bắt buộc khi sử dụng Docker không?

    Không bắt buộc, nhưng nó là cách tốt nhất để xây dựng image một cách tự động và có thể tái tạo. Bạn có thể tạo image bằng cách commit các thay đổi trong container đang chạy, nhưng cách này không được khuyến khích.

  2. Tôi có thể sử dụng bao nhiêu lệnh FROM trong một Dockerfile?

    Bạn có thể sử dụng nhiều lệnh FROM trong một Dockerfile để tạo ra multi-stage builds. Mỗi lệnh FROM sẽ bắt đầu một stage mới.

  3. Lệnh CMD và ENTRYPOINT khác nhau như thế nào?

    CMD cung cấp các tham số mặc định cho ENTRYPOINT hoặc có thể ghi đè ENTRYPOINT. ENTRYPOINT xác định lệnh chính sẽ chạy khi container được khởi động.

  4. Làm thế nào để giảm kích thước image Docker?

    Sử dụng base image nhỏ gọn, sử dụng multi-stage builds, loại bỏ các file không cần thiết, kết hợp các lệnh RUN, và sử dụng .dockerignore.

  5. Tôi có thể sử dụng biến môi trường trong Dockerfile không?

    Có, bạn có thể sử dụng lệnh ENV để thiết lập biến môi trường. Các biến này sẽ có sẵn trong container khi nó đang chạy.

  6. Làm thế nào để xử lý các bí mật (secrets) trong Dockerfile?

    Không nên lưu trữ trực tiếp bí mật trong Dockerfile. Sử dụng Docker Secrets hoặc các giải pháp quản lý bí mật khác.

  7. Tôi nên sử dụng COPY hay ADD?

    Nên sử dụng COPY vì nó đơn giản và dễ đoán hơn. Chỉ sử dụng ADD khi bạn cần tính năng giải nén file nén hoặc tải file từ URL.