Overview
Docker Image là một read-only template chứa mọi thứ cần thiết để tạo container: OS base, runtime, dependencies, app code, config. Image được xây dựng từ Dockerfile — mỗi instruction tạo ra một layer. Các layer được stack lên nhau theo thứ tự, tạo thành filesystem hoàn chỉnh thông qua Union Filesystem.
First-principles: Image = tập hợp các layer read-only xếp chồng. Container = image + 1 writable layer trên cùng.
Key Concepts
1. Image Layer là gì?
Mỗi layer chứa một tập hợp thay đổi filesystem (additions, deletions, modifications) so với layer trước đó.
- Mỗi instruction trong Dockerfile thay đổi filesystem sẽ tạo một layer mới (
FROM,RUN,COPY,ADD) - Các instruction chỉ thay đổi metadata thì không tạo layer (
CMD,LABEL,ENV,EXPOSE,ENTRYPOINT) - Mỗi layer sau khi tạo ra là immutable (bất biến, không thể sửa)
2. Dockerfile → Layer mapping
Xét Dockerfile mẫu cho PHP/Laravel:
# Layer 1: Base OS (FROM tạo layer đầu tiên)
FROM php:8.3-fpm-alpine
# Metadata only — KHÔNG tạo layer
LABEL maintainer="trung@example.com"
# Layer 2: Cài system dependencies
RUN apk add --no-cache \
libpng-dev \
libjpeg-turbo-dev \
libzip-dev \
&& docker-php-ext-install pdo_mysql zip gd
# Layer 3: Cài Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Layer 4: Copy composer files (tận dụng cache!)
COPY composer.json composer.lock ./
# Layer 5: Install PHP dependencies
RUN composer install --no-dev --no-scripts --no-autoloader
# Layer 6: Copy toàn bộ source code
COPY . .
# Layer 7: Generate autoloader
RUN composer dump-autoload --optimize
# Metadata only — KHÔNG tạo layer
EXPOSE 9000
CMD ["php-fpm"]
| Instruction | Tạo Layer? | Giải thích |
|---|---|---|
FROM |
✅ Có | Tạo base layer từ image gốc |
LABEL |
❌ Không | Chỉ thêm metadata |
RUN |
✅ Có | Thực thi lệnh → ghi thay đổi filesystem vào layer mới |
COPY / ADD |
✅ Có | Copy file từ build context vào image |
ENV |
❌ Không | Set biến môi trường (metadata) |
EXPOSE |
❌ Không | Khai báo port (metadata, không mở port thực tế) |
CMD / ENTRYPOINT |
❌ Không | Khai báo lệnh chạy mặc định (metadata) |
WORKDIR |
✅ Có (nếu tạo dir mới) | Set working directory — tạo layer nếu directory chưa tồn tại |
3. Content-Addressable Storage
Mỗi layer được định danh bằng SHA256 hash của nội dung. Điều này có nghĩa:
- Hai layer có cùng nội dung → cùng hash → chỉ lưu 1 lần trên disk
- Nhiều image có thể share layer giống nhau
- Khi pull image, Docker chỉ tải về layer chưa có locally
# Xem các layer của image
docker image inspect --format '.RootFS.Layers' php:8.3-fpm-alpine
# Xem chi tiết từng layer và size
docker image history php:8.3-fpm-alpine
How It Works — Union Filesystem & Copy-on-Write
Union Filesystem (UnionFS)
Docker sử dụng Union Filesystem (mặc định là overlay2) để xếp chồng các layer:
- Mỗi layer sau khi download được giải nén vào directory riêng trên host (
/var/lib/docker/overlay2/) - Khi tạo container, Union Filesystem merge tất cả layer thành một unified view duy nhất
- Root directory của container được set bằng
chrootvào unified directory này
┌─────────────────────────────────┐
│ Writable Container Layer │ ← Chỉ container này có
├─────────────────────────────────┤
│ Layer 7: composer dump-auto │ ← Read-only
├─────────────────────────────────┤
│ Layer 6: COPY . . │ ← Read-only
├─────────────────────────────────┤
│ Layer 5: composer install │ ← Read-only
├─────────────────────────────────┤
│ Layer 4: COPY composer.* │ ← Read-only
├─────────────────────────────────┤
│ Layer 3: COPY composer binary │ ← Read-only
├─────────────────────────────────┤
│ Layer 2: RUN apk add + ext │ ← Read-only
├─────────────────────────────────┤
│ Layer 1: php:8.3-fpm-alpine │ ← Read-only (base)
└─────────────────────────────────┘
Copy-on-Write (CoW)
Khi container cần đọc file → tìm từ layer trên xuống, dùng file tìm được đầu tiên.
Khi container cần ghi/sửa file:
- Tìm file trong các image layer (từ trên xuống)
- Copy file lên writable container layer (
copy_upoperation) - Mọi thay đổi chỉ xảy ra trên bản copy này
- File gốc trong image layer không bao giờ bị thay đổi
Ý nghĩa thực tế của CoW
- Nhiều container cùng image → share 100% read-only layers → tiết kiệm disk
docker ps --sizecho thấy:size= dung lượng writable layer riêng của containervirtual size= read-only layers + writable layer
- 5 container cùng image 100MB → chỉ tốn ~100MB + (5 × writable layer size), không phải 500MB
Layer Caching — Tối ưu build time
Cơ chế cache
Khi build image, Docker kiểm tra từng instruction theo thứ tự:
- Nếu instruction + context không thay đổi → dùng cached layer (cache hit ✅)
- Nếu có thay đổi → rebuild layer đó + tất cả layer sau (cache invalidation ❌)
Ví dụ: Tại sao COPY composer.json trước COPY . .
❌ Cách tệ — mỗi lần sửa bất kỳ file nào, composer install phải chạy lại:
COPY . . # Layer A: cache miss mỗi khi bất kỳ file nào thay đổi
RUN composer install # Layer B: phải rebuild vì Layer A đã miss
✅ Cách tốt — chỉ rebuild composer install khi dependencies thực sự thay đổi:
COPY composer.json composer.lock ./ # Layer A: chỉ miss khi composer.json/lock thay đổi
RUN composer install # Layer B: cache hit nếu Layer A hit
COPY . . # Layer C: miss khi code thay đổi, nhưng B không bị ảnh hưởng
Thứ tự instruction tối ưu cho Laravel
1. FROM ← Ít thay đổi nhất (base image)
2. RUN apk add ← System deps, hiếm khi đổi
3. COPY composer.json composer.lock
4. RUN composer install ← Chỉ rebuild khi deps thay đổi
5. COPY . . ← Code thay đổi thường xuyên nhất
6. RUN artisan ← Post-build commands
Các lệnh Dockerfile chi tiết
FROM
FROM <image>:<tag>
- Khởi tạo build stage mới, set base image
- Mỗi Dockerfile phải bắt đầu bằng
FROM(trừARGtrướcFROM) - Tag nên pin cụ thể để đảm bảo reproducible builds:
php:8.3-fpm-alpinethay vìphp:latest
RUN
# Exec form (khuyến nghị)
RUN ["apt-get", "install", "-y", "nginx"]
# Shell form
RUN apt-get update && apt-get install -y nginx
- Thực thi command trong layer mới
- Gộp nhiều RUN để giảm số layer:
# ❌ 3 layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# ✅ 1 layer + cleanup trong cùng layer
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
COPY vs ADD
| Tiêu chí | COPY | ADD |
|---|---|---|
| Copy file từ build context | ✅ | ✅ |
Auto-extract .tar.gz |
❌ | ✅ |
| Download từ URL | ❌ | ✅ (không khuyến nghị) |
| Khuyến nghị | Dùng mặc định | Chỉ khi cần extract tar |
WORKDIR
WORKDIR /var/www/html
- Set working directory cho
RUN,CMD,ENTRYPOINT,COPY,ADDphía sau - Nếu directory chưa tồn tại → tự động tạo
- Luôn dùng
WORKDIRthay vìRUN cd /path && ...
CMD vs ENTRYPOINT
| Tiêu chí | CMD | ENTRYPOINT |
|---|---|---|
| Mục đích | Default command, dễ override | Fixed executable, khó override |
Override khi docker run |
Bị thay thế hoàn toàn | Args được append vào |
| Kết hợp | CMD cung cấp default args cho ENTRYPOINT | ENTRYPOINT là main executable |
# CMD: user có thể override hoàn toàn
CMD ["php-fpm"]
# docker run myapp nginx ← chạy nginx thay vì php-fpm
# ENTRYPOINT + CMD: fixed executable + default args
ENTRYPOINT ["php"]
CMD ["artisan", "serve"]
# docker run myapp artisan migrate ← chạy php artisan migrate
Kiểm tra Layer trong thực tế
docker image history
$ docker image history my-laravel-app
IMAGE CREATED CREATED BY SIZE
a1b2c3d4e5f6 2 min ago CMD ["php-fpm"] 0B
<missing> 2 min ago COPY . . # buildkit 15.2MB
<missing> 2 min ago RUN composer install --no-dev # buildkit 45.3MB
<missing> 2 min ago COPY composer.json composer.lock ./ # buildkit 3.2KB
<missing> 5 min ago RUN apk add --no-cache ... # buildkit 12.1MB
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["php-fpm"] 0B
<missing> 3 weeks ago ... 85.4MB
Đọc kết quả:
- Size = 0B → metadata layer, không chiếm dung lượng
<missing>trong IMAGE column → layer được build trên hệ thống khác hoặc bởi BuildKit- Layer trên cùng = instruction cuối cùng trong Dockerfile
docker image inspect
# Xem tất cả layer SHA256
docker image inspect --format '.RootFS.Layers' my-laravel-app
# Xem tổng size
docker image inspect --format '.Size' my-laravel-app
docker system df
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 2 1.024GB 756.2MB (73%)
Containers 2 1 5.312MB 5.12MB (96%)
Local Volumes 3 2 256.1MB 128MB (50%)
Build Cache 12 0 312.4MB 312.4MB (100%)
Trade-offs
| Pros | Cons |
|---|---|
| Layer sharing giảm disk và bandwidth | File xóa ở layer sau vẫn tồn tại ở layer trước → image size phình |
| Layer caching tăng tốc build đáng kể | Cache invalidation lan truyền (1 layer miss → tất cả sau miss) |
| Immutable layers → reproducible, dễ rollback | Nhiều layer quá → tăng pull time và overhead |
| CoW cho phép nhiều container share image | CoW copy_up gây performance hit cho file lớn lần đầu sửa |
Thực hành
Bài 1: Build PHP image đơn giản và phân tích layer
# 1. Tạo Dockerfile
cat > Dockerfile <<'EOF'
FROM php:8.3-cli-alpine
RUN apk add --no-cache curl
COPY . /app
WORKDIR /app
CMD ["php", "-S", "0.0.0.0:8000"]
EOF
# 2. Tạo file test
echo '<?php echo "Hello Docker!";' > index.php
# 3. Build image
docker build -t php-layer-test .
# 4. Phân tích layers
docker image history php-layer-test
docker image inspect --format '.RootFS.Layers' php-layer-test
Bài 2: Quan sát cache hit/miss
# Build lần 1 — tất cả layer mới
docker build -t cache-test .
# Sửa index.php
echo '<?php echo "Updated!";' > index.php
# Build lần 2 — quan sát:
# - FROM, RUN apk: CACHED ✅
# - COPY . /app: REBUILD ❌ (file thay đổi)
docker build -t cache-test .
Bài 3: Chứng minh xóa file không giảm size
cat > Dockerfile.fat <<'EOF'
FROM alpine
RUN dd if=/dev/zero of=/bigfile bs=1M count=100
RUN rm /bigfile
EOF
cat > Dockerfile.slim <<'EOF'
FROM alpine
RUN dd if=/dev/zero of=/bigfile bs=1M count=100 && rm /bigfile
EOF
docker build -t fat-image -f Dockerfile.fat .
docker build -t slim-image -f Dockerfile.slim .
# So sánh size
docker image ls | grep -E 'fat|slim'
# fat-image: ~105MB (bigfile vẫn tồn tại trong layer trước)
# slim-image: ~7MB (bigfile tạo và xóa trong cùng layer)
Bài 4: Kiểm tra layer sharing giữa nhiều container
# Chạy 3 container cùng image
docker run -d --name c1 php-layer-test
docker run -d --name c2 php-layer-test
docker run -d --name c3 php-layer-test
# Kiểm tra size — writable layer gần bằng 0
docker ps --size --format 'table .Names\t.Size'
Checklist kiểm tra hiểu biết
- Giải thích được image layer là gì và tại sao immutable
- Phân biệt được instruction nào tạo layer, instruction nào không
- Giải thích được cơ chế Copy-on-Write
- Giải thích được tại sao
COPY composer.jsontrướcCOPY . .giúp tối ưu cache - Chứng minh được xóa file ở RUN riêng không giảm image size
- Dùng
docker image historyphân tích layer structure của bất kỳ image nào