Manifest
KHOÁ DOCKERTỜ 04 / 34CHƯƠNG 01SỬA ĐỔI C
Tờ 04 · Chương 01Nhập môn

ImageHai mươi chỉ thị đi vào, tám lớp đi ra — và chỗ chênh nằm ở đâu

Tờ 03 để lại một câu hỏi: vì sao đúng tám dòng Pull complete? Tờ này trả lời bằng số kiểm chứng được, rồi đi tiếp tới chỗ thứ tự hai dòng trong Dockerfile quyết định bạn chờ hai giây hay mười ba giây.

Thời lượng22 phút
Bản vẽ05 hình
Trình độNhập môn
Truy cậpMiễn phí
Dockerfile · 12 chỉ thịENV · CMD · LABEL · EXPOSEchỉ sửa tờ khai đi kèm image — SIZE 0BDockerfile · 8 chỉ thịADD · COPY · RUNchạm vào hệ thống tệp — mỗi chỉ thị một lớpCông thứcImage · thứ thật sự tải vềnginx:1.29-alpine8 lớp · 26MB8 dòng Pull complete của Tờ 03, không phải 20RootFS.Layers kiểm chứng độc lập: đúng 8 phần tửĐọc từ trên xuống — chỉ chỉ thị chạm vào tệp mới sinh ra lớp
01Hiện trường

Hai mươi chỉ thị, tám lớp

Tờ 03 kết thúc bằng một câu hỏi treo lại. Lúc kéo nginx:1.29-alpine về, màn hình in ra tám dòng Pull complete. Không phải mười, không phải hai mươi. Tám. Con số đó từ đâu ra?

bash · công thức làm ra image, đọc từ dưới lên
$ docker image history nginx:1.29-alpine
IMAGE          CREATED        CREATED BY                     SIZE
5616878291a2   4 months ago   RUN /bin/sh -c set -x && …  51.8MB
<missing>     4 months ago   ENV ACME_VERSION=0.3.1         0B
<missing>     4 months ago   ENV NJS_RELEASE=1              0B
<missing>     4 months ago   CMD ["nginx" …]         0B
<missing>     4 months ago   STOPSIGNAL SIGQUIT             0B
<missing>     4 months ago   EXPOSE map[80/tcp:{}] 0B
<missing>     4 months ago   COPY 30-tune-worker…     16.4kB
<missing>     4 months ago   COPY docker-entrypoint…  8.19kB
<missing>     4 months ago   RUN /bin/sh -c addgroup… 5.59MB
<missing>     4 months ago   ENV NGINX_VERSION=1.29.8       0B
<missing>     4 months ago   ADD alpine-minirootfs…   9.11MB

# 20 dòng. Chỉ 8 dòng có SIZE khác 0B.
02Giải phẫu

Chỉ những chỉ thị chạm vào hệ thống tệp mới sinh ra lớp

Đây là luật trung tâm của cả bài, và nó gọn tới mức đáng học thuộc: lớp là ảnh chụp thay đổi của hệ thống tệp. Chỉ thị nào không đụng tới tệp thì không có gì để chụp.

HÌNH 01
Hai mươi chỉ thị, tám lớp — chỉ thị nào sinh ra lớp nào
Bước 1/8nginx:1.29-alpine

Lớp 1, mã băm 29df493baa13, dung lượng 9.11MB. Sinh ra bởi chỉ thị ADD alpine-minirootfs. Đang hiện đủ hai mươi chỉ thị, gồm cả mười hai chỉ thị không sinh lớp.

DOCKERFILE · 20 CHI THIIMAGE · 8 LOPdiff_id thật, đọc từ RootFS.LayersRUN set -x && apkArch=… 51.8MBENV ACME_VERSION=0.3.1 0BENV NJS_RELEASE=1 0BENV NJS_VERSION=0.9.6 0BCMD ["nginx" "-g" "daemon off;"] 0BSTOPSIGNAL SIGQUIT 0BEXPOSE map[80/tcp:{}] 0BENTRYPOINT ["/docker-entrypoint.sh"] 0BCOPY 30-tune-worker-processes.sh 16.4kBCOPY 20-envsubst-on-templates.sh 12.3kBCOPY 15-local-resolvers.envsh 12.3kBCOPY 10-listen-on-ipv6-by-default.sh 12.3kBCOPY docker-entrypoint.sh / 8.19kBRUN set -x && addgroup -g 101… 5.59MBENV DYNPKG_RELEASE=1 0BENV PKG_RELEASE=1 0BENV NGINX_VERSION=1.29.8 0BLABEL maintainer=NGINX Docker… 0BCMD ["/bin/sh"] 0BADD alpine-minirootfs-3.23.4… 9.11MB1 · 29df493baa13… 9.11MB2 · e6082cdc2516… 5.59MB3 · f4eddaa4c303… 8.19kB4 · 00a43f630638… 12.3kB5 · f7b74afe8798… 12.3kB6 · 646d544dc619… 12.3kB7 · 2b12ccfb247a… 16.4kB8 · 36c003a300d1… 51.8MBBấm thẳng vào một lớp hoặc một chỉ thị để nhảy tới bước của nó
Chỉ thịADD alpine-minirootfs-3.23.4-x86_64.tar.gz /
diff_id29df493baa13…
1 / 8
bash · lọc ra đúng những chỉ thị đã sinh lớp
$ docker image history nginx:1.29-alpine --no-trunc \
    --format '{{.Size}}\t{{.CreatedBy}}' | grep -v '^0B'
51.8MB	RUN /bin/sh -c set -x && apkArch=…
16.4kB	COPY 30-tune-worker-processes.sh …
12.3kB	COPY 20-envsubst-on-templates.sh …
12.3kB	COPY 15-local-resolvers.envsh …
12.3kB	COPY 10-listen-on-ipv6-by-default.sh …
8.19kB	COPY docker-entrypoint.sh / # buildkit
5.59MB	RUN /bin/sh -c set -x && addgroup -g 101 …
9.11MB	ADD alpine-minirootfs-3.23.4-x86_64.tar.gz /
bash · đếm bằng máy, không đếm bằng mắt
$ docker image history nginx:1.29-alpine --format '{{.Size}}' | wc -l
20

$ docker image inspect nginx:1.29-alpine --format '{{len .RootFS.Layers}}'
8
Hai cách đếm, cùng một con số

Hai mươi dòng lịch sử, tám dòng có SIZE khác 0B. Và RootFS.Layers — danh sách containerd thật sự dùng — cũng cho đúng tám. Đó chính là tám dòng Pull complete mà Tờ 03 để lại chưa giải thích.

03Ẩn dụ

Chồng giấy can

Hiểu nôm na

Image là một chồng giấy can trên bàn vẽ

Người vẽ kỹ thuật không vẽ lại cả bản vẽ mỗi lần sửa. Họ đặt một tờ giấy can mới lên trên và chỉ vẽ phần thay đổi. Tờ nền là mặt bằng, tờ thứ hai là hệ điện, tờ thứ ba là hệ nước. Nhìn thẳng từ trên xuống qua cả chồng thì thấy một bản vẽ hoàn chỉnh, dù chưa tờ nào chứa trọn nó.

Ở bàn vẽTrong image
Tờ nền dưới cùngADD alpine-minirootfs… — 9.11MB
Mỗi tờ can đặt thêmMột lớp, sinh bởi RUN, COPY hoặc ADD
Nhìn từ trên xuốngHệ thống tệp mà tiến trình trong container thấy
Ghi chú viết ở lề chồng giấyENV, CMD, EXPOSE — siêu dữ liệu, không phải tờ nào cả
Tờ giấy trắng đặt trên cùng để nhápLớp ghi của container, đã gặp ở Tờ 03
Hai bản vẽ dùng chung mấy tờ nềnHai image dùng chung lớp trên cùng một máy
Ẩn dụ này sai ở chỗ

Ba chỗ, và chỗ đầu tiên là chỗ làm lộ bí mật.

Một — giấy can trong suốt, tờ trên không xoá được tờ dưới. Lớp image thì che khuất. Bạn RUN rm /app/khoa.pem ở một lớp sau, nhìn vào container thì tệp đã biến mất — nhưng nó vẫn nằm nguyên trong lớp dưới, vẫn được tải về cùng image, và ai kéo image đó về cũng trích ra được. Bí mật lỡ đi vào một lớp thì phải dựng lại image, không có cách xoá tại chỗ.

Hai — ở bàn vẽ bạn rút một tờ ở giữa ra rồi đặt tờ khác vào. Lớp image thì không. Mỗi lớp được định danh bằng một mã băm tính trên toàn bộ chồng bên dưới nó, nên sửa một tờ ở giữa là mọi tờ nằm trên nó thành tờ khác. Đây chính là cơ chế đứng sau chuyện bộ nhớ đệm bị phá ở mục 06.

Ba — chồng giấy can là của riêng bản vẽ đó. Lớp image thì dùng chung, và dùng chung thật chứ không phải nói cho vui: hai image khác nhau trên cùng một máy trỏ vào cùng một tệp lớp trên đĩa. Mục 05 có bằng chứng bằng mã băm.

04Danh mục

Tờ công thức, đọc từ trên xuống

Dockerfile là tệp văn bản liệt kê các chỉ thị, mỗi dòng một bước. BuildKit đọc từ trên xuống, chạy từng bước trong một container tạm, rồi đóng gói kết quả thành lớp.

HÌNH 02
Bản đồ chỉ thị Dockerfile — ba nhóm, ba số phận
Bước 1/14

FROM, nhóm sinh lớp. Cú pháp FROM tên image kèm tag. Chỗ hay sai: bỏ tag thì thành latest, và latest di chuyển được.

SINH LOPSIEU DU LIEUCHI LUC DUNG★ đọc tiếp ở bài 05 ⚠ chỗ hay dùng saichết khi dựng xongFROMRUNCOPYADD ⚠WORKDIRENVCMDENTRYPOINTEXPOSE ⚠LABELUSERSTOPSIGNALVOLUME ★ARGBấm một chỉ thị để mở khay tra cứu ba phần
Cú phápFROM <image>[:<tag>]
Ví dụ thậtFROM python:3.12-alpine
Chỗ hay saiBỏ tag thì thành latest, và latest là cái nhãn dán được sang image khác bất cứ lúc nào.
Chỉ thịViệcSinh lớp
FROMChọn image nền để xây tiếpKế thừa lớp của image nền
WORKDIRĐặt thư mục làm việc cho các bước sauCó, nhưng chỉ vài kB
COPYChép tệp từ ngữ cảnh dựng vào image
RUNChạy lệnh lúc dựng, giữ lại kết quả
ENVĐặt biến môi trường, còn hiệu lực lúc chạyKhông
EXPOSEGhi chú cổng mà image dự định ngheKhông
CMDLệnh mặc định khi tạo containerKhông
EXPOSE không mở cổng

Nó là một dòng ghi chú trong siêu dữ liệu. Tờ 03 đã gặp hậu quả của việc hiểu nhầm điều này: cột PORTS in ra 80/tcp đứng trơ một mình chính là một EXPOSE không ai công bố. Muốn mở cổng thì vẫn phải có -p lúc chạy.

DạngViếtChạy ra sao
execCMD ["python", "app.py"]Chạy thẳng, tiến trình của bạn là PID 1, nhận đủ tín hiệu
shellCMD python app.pyBọc trong /bin/sh -c, shell là PID 1, tín hiệu dừng có thể không tới ứng dụng
05Tay làm

Một ứng dụng nhỏ, dựng từ đầu

Lý thuyết đủ rồi. Dựng một ứng dụng web Python thật, ba tệp, và đọc từng dòng BuildKit in ra.

bash · ba tệp trong thư mục dự án
$ cat requirements.txt
flask==3.1.0

$ cat Dockerfile
FROM python:3.12-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]

Thứ tự năm chỉ thị giữa FROMCMD không phải ngẫu nhiên. Mục 06 sẽ đo bằng giây cái giá của việc xếp sai thứ tự đó.

bash · dựng lần đầu, không dùng đệm
$ docker build --no-cache -t sotay:1 .
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 202B done
#3 [internal] load .dockerignore
#4 [internal] load build context
#4 transferring context: 63B done
#5 [1/5] FROM docker.io/library/python:3.12-alpine@…
#6 [2/5] WORKDIR /app
#6 CACHED
#7 [3/5] COPY requirements.txt .
#7 DONE 0.4s
#8 [4/5] RUN pip install --no-cache-dir -r requirements.txt
#8 7.888 Successfully installed flask-3.1.0 Jinja2-3.1.6 …
#8 DONE 8.7s
#9 [5/5] COPY app.py .
#10 exporting to image
#10 naming to docker.io/library/sotay:1 0.0s done

Đọc nhãn [3/5], [4/5]: BuildKit đánh số các bước sinh lớp. Chú ý là thứ tự in ra không theo số #7 [2/5] có thể hiện sau #6 [3/5], vì BuildKit chạy song song những bước không phụ thuộc nhau.

bash · bằng chứng lớp dùng chung, bằng mã băm
$ diff <(docker image inspect sotay:1 --format '…') \
       <(docker image inspect sotay:3 --format '…')
8c8
< sha256:b3f7deb3f6b1c57d5ee23d7c017ce5b07ff5ee17049a63ac0eaa635c…
---
> sha256:0d1ee240a4be017e6f557c7311ee6f75bd53d1aa39023bcfd60fec1e…

# Chỉ lớp thứ tám khác — đó là lớp chứa app.py.
06Cơ chế

Sửa một dòng mã, chờ hai giây hay mười ba giây

Bộ đệm dựng làm việc theo một luật đơn giản và tàn nhẫn: đi từ trên xuống, gặp bước đầu tiên có gì đó đổi thì bước đó và toàn bộ bước phía dưới đều phải chạy lại.

HÌNH 03
Sửa một dòng mã — bộ đệm giữ được bao nhiêu
Bước 1/3cùng ứng dụng · khác thứ tự hai chỉ thị

Nấc chưa sửa gì. Bản xếp đúng: không bước nào chạy lại. Bản xếp sai: không bước nào chạy lại. Cả hai hết khoảng 0,9 giây.

XEP DUNG · 5 BUOCXEP SAI · 4 BUOCchỉ nấc giữa là số đo thật; hai nấc còn lại suy ra từ cùng phép đo1 FROM python:3.12-alpineCACHED2 WORKDIR /appCACHED3 COPY requirements.txt .CACHED4 RUN pip install …CACHED5 COPY app.py .CACHED1 FROM python:3.12-alpineCACHED2 WORKDIR /appCACHED3 COPY . .CACHED4 RUN pip install …CACHED0,9 s0,9 s
Ghi chúƯớc lượng suy ra từ phép đo ở nấc giữa, không phải số bấm giờ.
1 / 3
bash · sửa một dòng trong app.py rồi dựng lại bản xếp đúng
$ docker build -t sotay:3 .
#6 [3/5] COPY requirements.txt .
#6 CACHED
#8 [4/5] RUN pip install --no-cache-dir -r requirements.txt
#8 CACHED
#9 [5/5] COPY app.py .
#9 DONE 0.2s

real	0m2.122s
bash · cùng thay đổi đó, dựng lại bản xếp sai
$ docker build -f Dockerfile.xau -t sotay:xau2 .
#6 [2/4] WORKDIR /app
#6 CACHED
#7 [3/4] COPY . .
#8 [4/4] RUN pip install --no-cache-dir -r requirements.txt
#8 8.944 Successfully installed flask-3.1.0 …

real	0m12.983s

COPY . . chép cả app.py vào cùng một lớp với requirements.txt. Sửa app.py là lớp đó đổi, nên pip install nằm ngay dưới buộc phải chạy lại dù danh sách gói không đổi một chữ.

Luật xếp thứ tự, gói trong một câu

Thứ ít đổi đặt lên trên, thứ hay đổi đặt xuống dưới. Khai báo phụ thuộc đổi vài tuần một lần; mã nguồn đổi vài phút một lần. Vậy khai báo phụ thuộc phải nằm trên.

07Cơ chế

Dấu chấm ở cuối lệnh không phải trang trí

docker build -t sotay:1 . — dấu chấm đó là ngữ cảnh dựng. Trước khi chạy chỉ thị nào, Docker gói cả thư mục đó lại và gửi sang bộ dựng. Cả thư mục, kể cả thứ bạn quên mất là mình có.

HÌNH 04
Dấu chấm ở cuối lệnh gửi cả thư mục đi
Bước 1/2du -sh . = 53M

Bước 1. Dấu chấm cuối lệnh docker build là ngữ cảnh dựng. Docker gói cả thư mục đó lại và gửi sang bộ dựng, kể cả thư mục .git nặng bốn mươi mê-ga. Không có tệp lọc. Ngữ cảnh dựng là 54,54 mê-ga, và rác nằm lại trong image, nặng 203 mê-ga.

THU MUC DU ANBUILDKIT.git/40M__pycache__/12Mapp.py189Brequirements.txt13BDockerfile202Bdựng imageTHOI GIAN TRUYENDUNG LUONG IMAGE TAO RA.dockerignorechưa có tệp lọc54,54 MB0.6s transferring context: 54.54MB203MB rác nằm lại trong imageBật công tắc để thấy ống co lại
Bằng chứng#4 [internal] load build context #4 transferring context: 54.54MB 0.6s done
1 / 2
bash · trước và sau khi thêm .dockerignore
$ du -sh .
53M	.

$ docker build -f Dockerfile.xau -t sotay:beo .
#4 [internal] load build context
#4 transferring context: 54.54MB 0.6s done

$ cat .dockerignore
.git
__pycache__
*.pyc

$ docker build -f Dockerfile.xau -t sotay:gon .
#4 [internal] load build context
#4 transferring context: 188B 0.0s done
bash · cái giá nằm trên đĩa
$ docker image ls sotay
WARNING: This output is designed for human readability.
IMAGE        ID             DISK USAGE   CONTENT SIZE
sotay:beo    79cf08ca91e9        203MB         77.6MB
sotay:gon    ff0faebae84a       94.3MB           23MB

Từ 54,54 MB xuống 188 B. Và vì Dockerfile đó dùng COPY . ., đống rác kia không chỉ đi qua đường ống — nó nằm lại trong image.

08Đối chiếu

latest không có nghĩa là mới nhất

Tag là một cái nhãn dán, và nhãn thì gỡ ra dán sang chỗ khác được. Digest là mã băm của chính nội dung, không đổi được.

HÌNH 05
Tag là cái nhãn dán, digest là chính nội dung
Bước 1/2

Trước khi phát hành. Cả bốn cách gọi tên đều trỏ vào bản kê khai A, digest sha256 bắt đầu bằng 5616878291a2. Lúc này chúng cho ra cùng một image.

CACH GOI TENBAN KE KHAItag phiên bản ít di chuyển hơn, nhưng vẫn di chuyển được; chỉ digest là bất biếnđã trượtnginx:latestnginx:alpinenginx:1.29-alpinenginx@sha256:5616…KHONG DOI DUOCbản kê khai Asha256:5616878291a2eed5…bản kê khai Bsha256:db35bfc6b2951e7f…
Trên máy thậtnginx:alpine → sha256:db35bfc6b295… nginx:1.29-alpine → sha256:5616878291a2…
Đọc gì từ đóHai tag trên cùng một máy đang trỏ vào hai image khác nhau, vì chúng được kéo về vào hai ngày khác nhau.
1 / 2
bash · hai tag, hai image khác nhau, trên cùng một máy
$ docker image inspect nginx:alpine --format 'alpine -> {{.ID}}'
alpine  -> sha256:db35bfc6b2951e7f8a72db5db120288c127ffaeeb4a6d…

$ docker image inspect nginx:1.29-alpine --format '1.29 -> {{.ID}}'
1.29    -> sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e…
Ghim tới đâu là đủ

Máy này kéo nginx:alpine về vào một ngày, nginx:1.29-alpine vào ngày khác. Hai mã băm khác nhau nghĩa là hai image khác nhau. Ai viết nginx:alpine trong tệp triển khai đang giao cho người khác quyền đổi phần mềm chạy trên máy chủ của mình.

09Bảng sửa đổi

Năm sai lầm tốn thời gian nhất khi dựng image

SỐSAI LẦM & CÁCH SỬA
01

Đặt COPY . . lên trước bước cài phụ thuộc

Sai lầm đắt nhất trong bài này, vì nó không báo lỗi — nó chỉ lặng lẽ lấy của bạn mười một giây mỗi lần dựng. Mã nguồn đổi là lớp chứa mã nguồn đổi, và mọi bước nằm dưới nó chạy lại từ đầu.

Cách sửaChép tệp khai báo phụ thuộc trước, cài, rồi mới chép mã nguồn. Kiểm bằng cách sửa một dòng rồi dựng lại: bước cài phụ thuộc phải in ra CACHED.

02

Tưởng RUN rm xoá được tệp khỏi image

Ba lớp: lớp một chứa khoá, lớp ba dán một mẩu giấy đục lên nó. Khoá vẫn nằm trong image, vẫn được tải về cùng image, ai cũng trích ra được.

Cách sửaĐừng để bí mật đi vào lớp nào. Dùng --mount=type=secret của BuildKit, hoặc nạp bí mật lúc chạy. Đã lỡ đẩy image lên kho ảnh thì coi như khoá đã lộ và phải thu hồi khoá, không phải xoá image.

03

Dùng ADD cho việc mà COPY làm được

ADD có hai hành vi ngầm mà COPY không có: tự giải nén tệp nén cục bộ, và tải được URL. Cả hai đều là bất ngờ khó chịu khi bạn chỉ định chép một tệp.

Cách sửaMặc định dùng COPY. Chỉ dùng ADD khi bạn cố ý muốn giải nén một tệp tar. Cần tải từ mạng thì RUN với curl rõ ràng hơn, và kiểm tra được mã băm.

04

Dán lại MAINTAINER từ hướng dẫn cũ

Chỉ thị này đã bị bỏ từ lâu. Tài liệu năm 2017 còn đầy, nên nó vẫn được chép đi chép lại từ bài blog này sang bài blog khác.

Cách sửaLABEL org.opencontainers.image.authors="…". Nhãn theo chuẩn OCI thì công cụ khác đọc được, còn MAINTAINER thì không.

05

Dựng bằng bộ dựng cũ mà không biết

Dòng DEPRECATED: The legacy builder is deprecated trôi qua mà không ai đọc. Bộ dựng cũ không chạy song song, không có --mount=type=secret, và sắp bị gỡ hẳn.

Cách sửaCài gói docker-buildx-plugin từ kho chính thức của Docker, theo đúng cách Tờ 02 đã dựng. Kiểm bằng docker buildx version.

10Nghiệm thu

Kiểm tra hiểu bài

Năm câu, không giới hạn thời gian. Mỗi câu giải thích cả đáp án đúng lẫn vì sao các đáp án kia sai — đó mới là chỗ đổi được cách nghĩ.

Phiếu nghiệm thu · Tờ 040 / 5 câu
01docker image history nginx:1.29-alpine in ra hai mươi dòng, nhưng lúc kéo image về chỉ có tám dòng Pull complete. Vì sao?Chọn 1
02Bạn sửa một dòng trong app.py rồi dựng lại. Với Dockerfile nào dưới đây thì bước RUN pip install in ra CACHED?Chọn 1
03Một Dockerfile tải khoá bí mật về ở một RUN, dùng nó, rồi RUN rm khoa.pem ở bước sau. Khoá đó đã được xoá khỏi image.Đúng / Sai
04Chọn tất cả khẳng định đúng về ngữ cảnh dựng và tệp .dockerignore.Chọn nhiều
05Trên máy chủ thật, bạn phải chọn cách khai image trong tệp triển khai. Chọn tất cả khẳng định đúng.Chọn nhiều
Nộp cả phiếu để chấm
Kiểm hết năm câu ở trên rồi nộp phiếu — máy chủ mới là nơi chấm điểm thật.
11Tóm tắt

Năm điều mang về

01

Lớp là ảnh chụp thay đổi hệ thống tệp. Chỉ RUN, COPY, ADD sinh lớp. Hai mươi chỉ thị của nginx cho ra tám lớp — đúng tám dòng Pull complete của Tờ 03.

02

Lớp dùng chung được và không sửa tại chỗ được. Bảy trong tám lớp của hai image chỉ nằm một bản trên đĩa. Đổi một lớp là mọi lớp trên nó thành lớp khác.

03

Thứ tự chỉ thị là quyết định về tốc độ. Ít đổi lên trên, hay đổi xuống dưới. Đo được: 2,1 giây so với 13,0 giây cho cùng một thay đổi.

04

Dấu chấm cuối lệnh gửi cả thư mục đi. .dockerignore là tệp bắt buộc, không phải tuỳ chọn. 54,54 MB xuống 188 B.

05

Tag di chuyển được, digest thì không. latest là cái nhãn, không phải lời hứa. Trên máy chủ, ghim phiên bản là mức tối thiểu.

Còn một câu hỏi bài này cố tình chưa trả lời. Image chỉ đọc, lớp ghi của container thì mất cùng container — vậy dữ liệu người dùng, cơ sở dữ liệu, tệp tải lên sống ở đâu? Tờ 05 đóng chương một bằng đúng câu đó.

Xong tờ này rồi?
Đăng nhập để lưu tiến độ vào tài khoản của bạn và mở khoá chứng nhận cuối khoá. Bài học vẫn đọc được đầy đủ khi chưa đăng nhập.
Đăng nhập để lưu tiến độ