Phân tích
6 phút đọc15 tháng 6, 2026278

Cài Docker & chạy container đầu tiên không có gì phức tạp cả

Từ cài đặt trên macOS/Windows/Linux đến chạy Nginx container đầu tiên bài 2 trong series Docker từ A-Z.

N

Nguyễn Nhật Long

@nguyennhatlong1303

Cài Docker & chạy container đầu tiên không có gì phức tạp cả

Nếu bạn đã đọc bài 1 của series này thì chắc đã hiểu Docker là gì rồi. Giờ mình sẽ không nói lý thuyết nữa bài này hoàn toàn thực hành, mục tiêu là sau khi đọc xong bạn phải có một container đang chạy trên máy.

Cài Docker lên máy tùy OS mà làm khác nhau

macOS

Cái này đơn giản nhất. Vào docker.com, download Docker Desktop về, kéo vào thư mục Applications như mọi app macOS khác. Mở lên, nó sẽ hỏi quyền admin một lần, xong là chạy được.

Verify bằng lệnh này:

Terminal
1docker --version
2# Docker version 24.0.x, build ...

Nếu thấy version hiện ra là ổn. Docker Desktop trên Mac chạy một Linux VM ngầm bên dưới vì container về bản chất là Linux technology bạn không cần quan tâm chuyện đó, nó transparent hoàn toàn.

Một lưu ý nhỏ: Docker Desktop khá ngốn RAM, mặc định nó được cấp 8GB. Nếu máy bạn ít RAM thì vào Settings → Resources điều chỉnh lại cho hợp lý, mình thường để 4GB là đủ dùng cho dev.

Windows

Trên Windows thì có thêm một bước: bạn cần WSL2 (Windows Subsystem for Linux) trước. Mở PowerShell với quyền admin rồi chạy:

Terminal
1wsl --install

Restart máy, sau đó download Docker Desktop cho Windows và cài bình thường. Trong quá trình cài nó sẽ hỏi dùng WSL2 backend hay Hyper-V chọn WSL2, performance tốt hơn nhiều.

Sau khi cài xong restart thêm một lần nữa. Mình biết Windows hay bắt restart, nhưng lần này là cần thiết thật sự.

Linux (Ubuntu)

Cái này mình thích nhất vì không cần cài Docker Desktop dùng Docker Engine trực tiếp, nhẹ hơn nhiều. Chạy lệnh sau:

Terminal
1curl -fsSL https://get.docker.com | sh

Script này tự detect distro và cài đúng package. Sau khi cài xong, thêm user của bạn vào group docker để không phải gõ sudo mỗi lần:

Terminal
1sudo usermod -aG docker $USER

Log out rồi log in lại để group permission có hiệu lực. Test thử:

Terminal
1docker run hello-world

Nếu thấy message "Hello from Docker!" là thành công.

Những lệnh CLI bạn sẽ dùng hàng ngày

Trước khi chạy container thật, mình liệt kê nhanh các lệnh core mà bạn sẽ gõ đi gõ lại:

Nhớ hết ngay thì khó, nhưng run, ps, stop, rm, logs là 5 cái bạn sẽ nhớ tự nhiên sau vài ngày dùng.

LệnhTác dụng
`docker run <image>`Tạo và chạy container từ image
`docker ps`Xem các container đang chạy
`docker ps -a`Xem tất cả container kể cả đã stop
`docker stop <id/name>`Dừng container
`docker rm <id/name>`Xóa container
`docker logs <id/name>`Xem log của container
`docker exec -it <id/name> bash`SSH vào trong container
`docker images`Xem các image đang có trên máy
`docker pull <image>`Download image từ Docker Hub

Chạy Nginx container đầu tiên có ý nghĩa

hello-world chỉ để test, giờ mình chạy thứ gì thực tế hơn Nginx web server:

Terminal
1docker run -d -p 8080:80 --name my-nginx nginx

Mở browser vào http://localhost:8080 bạn sẽ thấy trang welcome của Nginx. Chỉ một lệnh, không cần cài gì thêm.

Giải thích từng flag:

  • -d (detached): chạy container ở background, không chiếm terminal của bạn. Nếu không có flag này, Ctrl+C là container dừng luôn.
  • -p 8080:80: đây là port mapping. Format là host_port:container_port. Nginx trong container lắng nghe port 80, nhưng mình map ra port 8080 của máy host để tránh conflict với các service khác.
  • --name my-nginx: đặt tên cho container. Không đặt thì Docker tự generate tên random kiểu happy_turing dùng được nhưng khó nhớ.

Kiểm tra container đang chạy:

Terminal
1docker ps
2# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3# a1b2c3d4e5f6 nginx ... ... Up ... 0.0.0.0:8080->80/tcp my-nginx

Xem log của Nginx (ví dụ khi debug request):

Terminal
1docker logs my-nginx
2docker logs -f my-nginx # follow mode, giống tail -f

Vào trong container để inspect:

Terminal
1docker exec -it my-nginx bash
2# Giờ bạn đang ở trong container rồi
3ls /etc/nginx/
4exit

Dọn dẹp:

Terminal
1docker stop my-nginx
2docker rm my-nginx

Port mapping và Volume mount hai khái niệm hay dùng nhất

Mình đã giải thích -p ở trên. Giờ nói về -v volume mount, cái này quan trọng không kém.

Mặc định, mọi thứ bên trong container là ephemeral container bị xóa là data mất hết. Volume mount giải quyết vấn đề đó bằng cách link một thư mục trên máy host vào trong container:

Terminal
1docker run -d \
2 -p 8080:80 \
3 --name my-nginx \
4 -v $(pwd)/html:/usr/share/nginx/html \
5 nginx

Lệnh này mount thư mục html ở thư mục hiện tại vào /usr/share/nginx/html trong container (đây là nơi Nginx serve static files). Bạn edit file trên máy host, Nginx trong container phản ánh ngay lập tức không cần rebuild hay restart gì cả.

Theo kinh nghiệm của mình, volume mount là thứ bạn sẽ dùng cực nhiều trong dev environment mount source code vào container để hot reload, mount config file để thay đổi mà không cần rebuild image, mount database data để data persist qua các lần restart container.

Tình huốngDùng flag
Expose service ra ngoài`-p host:container`
Persist data hoặc mount config`-v host_path:container_path`
Set biến môi trường`-e KEY=VALUE`
Giới hạn resource`--memory 512m --cpus 0.5`

Docker Hub và chuyện chọn image tag

Khi bạn gõ docker run nginx, Docker tự động pull image nginx:latest từ Docker Hub. Nhưng latest không phải lúc nào cũng là lựa chọn tốt nhất.

Vào hub.docker.com search bất kỳ image nào, bạn sẽ thấy hàng loạt tag. Với Nginx chẳng hạn:

Mình thấy cái này hay ở chỗ: cùng một software nhưng bạn có thể chọn đúng variant cho từng use case. Image alpine nhỏ hơn latest đến 10 lần, deploy nhanh hơn, tốn ít bandwidth hơn nhưng thiếu một số tools debug vì Alpine stripped down khá nhiều.

TagÝ nghĩaKhi nào dùng
`nginx:latest`Bản mới nhất, full sizeDev nhanh, không care size
`nginx:1.25`Version cụ thểProduction pin version để stable
`nginx:alpine`Dựa trên Alpine Linux, rất nhỏ (~5MB)Khi cần image nhỏ, production
`nginx:1.25-alpine`Version cụ thể + AlpineProduction tốt nhất

Anh em lưu ý: trong production không nên dùng latest vì tag này thay đổi theo thời gian hôm nay pull latest là version A, tuần sau pull lại có thể là version B với breaking changes. Pin version cụ thể như nginx:1.25-alpine để đảm bảo reproducible build.

Pull image về mà chưa chạy ngay:

Terminal
1docker pull nginx:alpine
2docker pull postgres:16-alpine

Xem các image đang có trên máy:

Terminal
1docker images
2# REPOSITORY TAG IMAGE ID CREATED SIZE
3# nginx latest ... ... 187MB
4# nginx alpine ... ... 41MB

Workflow thực tế khi dev

Sau khi quen với các lệnh cơ bản, flow của mình thường là:

Terminal
1# Pull image về (nếu chưa có)
2docker pull postgres:16-alpine
3
4# Chạy database với volume để data không mất
5docker run -d \
6 --name dev-postgres \
7 -e POSTGRES_PASSWORD=secret \
8 -e POSTGRES_DB=myapp \
9 -p 5432:5432 \
10 -v postgres_data:/var/lib/postgresql/data \
11 postgres:16-alpine
12
13# Kiểm tra đang chạy chưa
14docker ps
15
16# Xem log nếu có vấn đề
17docker logs dev-postgres
18
19# Khi xong việc, stop lại (data vẫn còn trong volume)
20docker stop dev-postgres
21
22# Hôm sau start lại tiếp
23docker start dev-postgres

Bạn sẽ nhận ra ngay cái hay: không cần cài PostgreSQL trực tiếp lên máy, không conflict version với project khác, xóa container đi là sạch hoàn toàn.

Bài tiếp theo mình sẽ nói về Dockerfile cách bạn tự build image cho ứng dụng của mình thay vì chỉ dùng image có sẵn từ Docker Hub. Đó mới là lúc Docker thật sự hữu ích cho workflow hàng ngày.

NN

Nguyễn Nhật Long

@nguyennhatlong1303

Nguyễn Nhật Long is a Senior Frontend Engineer and Frontend Team Leader with 7 years of experience building real-time fintech platforms. Specializing in React, Next.js, TypeScript, and React Native, shipping 10+ products across Web, Mobile, Telegram Mini-Apps, and Web3.

Thấy hay? Chia sẻ cho bạn bè!

Cài Docker & chạy container đầu tiên không có gì phức tạp cả — Stacklog