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.
Nguyễn Nhật Long
@nguyennhatlong1303
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:
1docker --version2# 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:
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:
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:
1sudo usermod -aG docker $USER
Log out rồi log in lại để group permission có hiệu lực. Test thử:
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ệnh | Tá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:
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ểuhappy_turingdùng được nhưng khó nhớ.
Kiểm tra container đang chạy:
1docker ps2# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES3# a1b2c3d4e5f6 nginx ... ... Up ... 0.0.0.0:8080->80/tcp my-nginx
Xem log của Nginx (ví dụ khi debug request):
1docker logs my-nginx2docker logs -f my-nginx # follow mode, giống tail -f
Vào trong container để inspect:
1docker exec -it my-nginx bash2# Giờ bạn đang ở trong container rồi3ls /etc/nginx/4exit
Dọn dẹp:
1docker stop my-nginx2docker 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:
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ống | Dù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ĩa | Khi nào dùng |
|---|---|---|
| `nginx:latest` | Bản mới nhất, full size | Dev 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ể + Alpine | Production 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:
1docker pull nginx:alpine2docker pull postgres:16-alpine
Xem các image đang có trên máy:
1docker images2# REPOSITORY TAG IMAGE ID CREATED SIZE3# nginx latest ... ... 187MB4# 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à:
1# Pull image về (nếu chưa có)2docker pull postgres:16-alpine34# Chạy database với volume để data không mất5docker 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-alpine1213# Kiểm tra đang chạy chưa14docker ps1516# Xem log nếu có vấn đề17docker logs dev-postgres1819# Khi xong việc, stop lại (data vẫn còn trong volume)20docker stop dev-postgres2122# Hôm sau start lại tiếp23docker 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.
Nguyễn Nhật Long
@nguyennhatlong1303Nguyễ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è!