# Docker cho FDE: khi image phải đi qua USB mới tới được server của khách

> Phần lớn kỹ sư học Docker để chạy code trên laptop; FDE phải học lại nó như một công cụ giao hàng vào nơi không có internet, không có root và không cùng loại chip.

Bản gốc: https://fdetimes.net/vi/cong-cu/docker-cho-fde/

Trong hướng dẫn DGX OS 8 của NVIDIA, bước đưa một container image lên máy chủ air-gapped được mô tả rất mộc: chép image sang thiết bị lưu trữ di động, chẳng hạn một chiếc USB, rồi mang tới hệ thống đích. Không registry, không `docker pull`. Một máy chủ DGX nhận phần mềm theo cách gần giống đĩa cài đặt của hai mươi năm trước.

Nếu bạn làm FDE, rất có thể bạn sẽ gặp đúng tình huống đó, và nó không chỉ xảy ra ở NVIDIA: Overleaf cũng viết hẳn một hướng dẫn cho khách triển khai offline. Dev viết code chạy trên laptop và CI; FDE phải làm cho nó chạy trong hạ tầng do khách kiểm soát, nơi có thể không có internet, cấm root và dùng loại chip khác máy bạn.

Vì thế Docker trong tay FDE không còn là tiện ích dev. Nó là công cụ giao hàng, và cách bạn viết Dockerfile quyết định phần mềm có được đội hạ tầng của khách cho chạy hay không.

## Image là gói hàng, không phải môi trường dev

Docker mô tả Docker Engine là công nghệ containerization mã nguồn mở để build và đóng gói ứng dụng. Định nghĩa đó đúng nhưng chưa đủ với FDE. Thứ bạn thực sự trao cho khách là một image, và mọi thứ nằm trong image đều là thứ đội bảo mật của khách sẽ soi.

Đây là lý do multi-stage build quan trọng hơn hẳn so với khi bạn chỉ chạy local. Bạn dùng nhiều lệnh `FROM` trong một Dockerfile, mỗi lệnh mở một stage mới; stage đầu có đủ compiler và thư viện để build, stage cuối chỉ copy artifact sang. Tài liệu Docker tóm kết quả bằng một câu: image production nhỏ gọn chỉ còn binary bên trong.

Ít thứ bên trong nghĩa là ít câu hỏi phải trả lời, ít dung lượng chép qua USB, ít lỗ hổng tiềm tàng. Cùng tinh thần đó, một file `.dockerignore` loại những file không liên quan khỏi bước build, và bạn không phải sắp xếp lại cấu trúc repo để làm điều đó.

## Ba câu hỏi bạn phải hỏi khách trước khi build

Câu hỏi thứ nhất: máy chủ có internet không? Nếu không, quy trình giao hàng dựa trên hai lệnh. `docker image save` ghi image ra file tar kèm toàn bộ parent layer và tag; `docker image load` nạp lại từ tar, kể cả khi đã nén bằng gzip, bzip2, xz hay zstd, và khôi phục cả tag.

Hướng dẫn on-prem của Overleaf mô tả đúng quy trình này: chuyển các file .tar sang server offline rồi `docker load` từng file.

Câu hỏi thứ hai: service có cần root không? Docker khuyến nghị dùng `USER` để chuyển sang non-root khi service chạy được mà không cần đặc quyền. Ở mức sâu hơn, rootless mode cho phép cả Docker daemon và container chạy dưới user thường, phù hợp với khách cấm daemon chạy root.

Câu hỏi thứ ba: server dùng chip gì? Hướng dẫn của NVIDIA yêu cầu pull image trên một máy có kết nối và cùng kiến trúc CPU với hệ thống air-gapped. Nếu bạn build trên laptop ARM rồi mang sang server x86, image sẽ không chạy. Multi-platform build với cờ `--platform` của buildx cho phép bạn chỉ định đích như `linux/amd64` hay `linux/arm64`.

**Điểm mấu chốt:** Hạ tầng khách lấy đi ba thứ bạn coi là mặc định: internet, root và kiến trúc CPU quen thuộc. Dockerfile tốt là Dockerfile đã tính trước cả ba.

## Một quy trình tối thiểu

Thử hình dung bạn giao một service nhỏ cho khách có server x86 offline. Dockerfile có thể trông như sau, với phần sau `@sha256:` là chỗ bạn điền digest thật của base image:

```dockerfile
FROM golang:1.22@sha256:... AS build
WORKDIR /src
COPY . .
RUN go build -o /out/server ./cmd/server

FROM alpine@sha256:...
COPY --from=build /out/server /server
USER 10001
ENTRYPOINT ["/server"]
```

Hai `FROM`, hai stage, chỉ binary đi vào image cuối, chạy dưới user không phải root. Base image được pin theo digest vì tag có thể thay đổi; Docker gọi đây là cách bảo toàn supply chain, còn với FDE nó đơn giản là bảo đảm image bạn test hôm nay giống hệt image khách nạp tháng sau.

Trên máy có mạng, bạn build đúng kiến trúc và đóng gói:

```bash
docker buildx build --platform linux/amd64 -t acme/agent:1.4.0 .
docker image save acme/agent:1.4.0 | gzip > agent-1.4.0.tar.gz
```

Trên server khách, sau khi file đã đi qua thiết bị lưu trữ di động:

```bash
docker image load -i agent-1.4.0.tar.gz
```

Tag `acme/agent:1.4.0` được khôi phục nguyên vẹn, và phần còn lại là chạy container như bình thường.

## Docker không thay bạn kiểm tra hạ tầng

Docker làm đúng những gì bạn yêu cầu; việc yêu cầu đó có khớp với server của khách hay không là trách nhiệm của bạn. NVIDIA không vô cớ đòi image phải được pull trên máy cùng kiến trúc CPU với hệ thống air-gapped: kiến trúc là thứ bạn phải xác nhận với khách trước khi gõ lệnh build, chứ không để tới lúc đứng cạnh server mới biết.

Tag không pin cũng vậy: vẫn build được, chỉ khác là lần build sau có thể cho ra image khác.

Rootless mode cũng là thứ thuộc phía daemon, tức là cấu hình trên máy của khách, không nằm trong image của bạn. Bạn có thể đề xuất, nhưng người bật nó là đội hạ tầng của họ.

Và `USER` non-root chỉ áp dụng khi service thực sự chạy được mà không cần đặc quyền; nếu code của bạn ghi vào đường dẫn cần root, bạn phải sửa code trước, không phải sửa Dockerfile.

## Học gì trước

Nếu bạn mới chuyển hướng sang FDE, đừng bắt đầu bằng orchestration. Hãy làm chủ một Dockerfile multi-stage, hiểu digest khác tag ở đâu, và làm trọn vòng build, save, load giữa hai máy khác nhau, một trong hai đã tắt mạng. Vòng lặp đó mất một buổi tối nhưng dạy bạn nhiều hơn một tuần đọc tài liệu.

Một lời khuyên khi đi tìm việc: nếu mô tả công việc nhắc tới on-prem, air-gapped hay deploy vào hạ tầng riêng của khách, hãy chuẩn bị sẵn một câu chuyện giao hàng offline để kể trong buổi phỏng vấn.

Trong CV, thay vì ghi "biết Docker", hãy ghi bạn đã đóng gói và giao một service chạy offline cho môi trường non-root, kèm dung lượng image trước và sau khi tối ưu.

Chiếc USB trong hướng dẫn của NVIDIA không phải dấu hiệu lạc hậu. Nó là lời nhắc rằng ở phía khách hàng, phần mềm chỉ được tính là xong khi nó chạy được ở nơi bạn không có quyền kiểm soát.

**Thử ngay tuần này:**

- Viết lại Dockerfile của một service bạn đang có thành multi-stage, pin base image theo digest và thêm USER non-root; so dung lượng image trước và sau.
- Build với --platform linux/amd64, docker image save ra file .tar.gz, chép sang một máy khác đã tắt mạng và docker image load rồi chạy thử.
- Ghi lại ba câu hỏi về hạ tầng (internet, root, CPU) thành checklist để gửi khách trước buổi deployment.

## Nguồn

- [Docker Engine](https://docs.docker.com/engine/)

- [Multi-stage builds](https://docs.docker.com/build/building/multi-stage/)

- [Building best practices](https://docs.docker.com/build/building/best-practices/)

- [Multi-platform builds](https://docs.docker.com/build/building/multi-platform/)

- [docker image save](https://docs.docker.com/reference/cli/docker/image/save/)

- [docker image load](https://docs.docker.com/reference/cli/docker/image/load/)

- [Rootless mode](https://docs.docker.com/engine/security/rootless/)

- [Air-gapped/offline deployments](https://docs.overleaf.com/on-premises/installation/air-gapped-offline-deployments)

- [Loading Container Images (NVIDIA DGX OS 8 User Guide)](https://docs.nvidia.com/dgx/dgx-os-8-user-guide/8.0.0/appendix_g_installing_docker_containers.html)
