---
id: docker-entrypoint
title: "CMD và ENTRYPOINT"
shortTitle: "CMD và ENTRYPOINT"
track: docker
trackTitle: "Docker"
order: 5
level: core
status: published
estimatedMinutes: 35
prerequisites:
  - docker-buildkit
next: docker-env
reviewedAgainst: 2026-10-19
sources:
  - title: "KodeKloud — Docker Training Course for the Absolute Beginner"
    url: https://learn.kodekloud.com/learn/courses/docker-training-course-for-the-absolute-beginner
  - title: "Dockerfile CMD"
    url: https://docs.docker.com/reference/dockerfile/#cmd
  - title: "Dockerfile ENTRYPOINT"
    url: https://docs.docker.com/reference/dockerfile/#entrypoint
---

# CMD và ENTRYPOINT

Image ở bài Dockerfile kết thúc bằng `CMD ["node", "server.js"]`. Gõ `docker run devops-lab-node:1` thì container chạy đúng lệnh đó. Gõ thêm chữ sau tên image thì lệnh đó biến mất, container chạy chữ vừa gõ. `ENTRYPOINT` khóa executable, phần gõ thêm được nối phía sau.

## Để làm gì

Chọn `CMD` khi image cần một lệnh mặc định mà người chạy được phép thay cả dòng. Chọn `ENTRYPOINT` khi image là một executable, argument lúc `docker run` chỉ là tham số.

## Gồm những gì

- `CMD` bị argument của `docker run` thay cả dòng.
- `ENTRYPOINT` dạng exec giữ executable. `CMD` là argument mặc định.
- Dạng shell (`ENTRYPOINT node server.js`) bỏ qua `CMD` và bỏ qua argument lúc chạy, process chính là shell.
- `--entrypoint` thay executable đã khóa.
- Khai `ENTRYPOINT` thì `CMD` kế thừa từ image gốc bị xóa. Muốn argument mặc định thì viết lại `CMD` trong cùng file.

Biến môi trường và file `.env` là bài sau. Signal tới PID 1 khi `CMD` dạng chuỗi đã nói ở bài Dockerfile.

## Dùng khi nào

App một process, lệnh start cố định, người vận hành đôi khi đổi tham số: `ENTRYPOINT` là binary, `CMD` là tham số mặc định.

Image dùng như toolbox, hôm nay chạy `sh`, hôm khác chạy một lệnh khác: chỉ `CMD`. Argument sau tên image thay cả lệnh.

## Kiến thức cốt lõi

Dockerfile nên có ít nhất một trong hai chỉ thị. Mỗi chỉ thị lấy dòng cuối cùng trong file. Dòng trên bị dòng dưới của cùng loại ghi đè.

Dạng exec là mảng JSON. Docker chạy đúng executable đó, không qua shell. Dạng shell là một chuỗi. Docker chạy `/bin/sh -c` với chuỗi đó. Process chính là shell.

| Trong Dockerfile | `docker run img` | `docker run img extra` |
| --- | --- | --- |
| `CMD ["node", "server.js"]` | `node server.js` | `extra` thay cả CMD |
| `ENTRYPOINT ["node"]` và `CMD ["server.js"]` | `node server.js` | `node extra` |
| `ENTRYPOINT node server.js` | `sh -c "node server.js"` | vẫn shell đó, `extra` bị bỏ |

Hàng cuối là dạng shell của `ENTRYPOINT`. Docs nói dạng này bỏ qua `CMD` và bỏ qua argument trên `docker run`. `docker stop` gửi signal tới shell, không tới `node`, trừ khi chuỗi tự `exec`.

`CMD` dạng chuỗi trong khi `ENTRYPOINT` là mảng JSON thì argument mặc định trở thành `/bin/sh -c ...` nối sau entrypoint. Cả hai phải cùng dạng exec khi viết chung.

Khai `ENTRYPOINT` trong Dockerfile này thì `CMD` của image `FROM` bị đặt thành rỗng. Image `node` gốc có `CMD` riêng. Sau khi file của bạn thêm `ENTRYPOINT ["node"]` mà không viết `CMD`, `docker run` không còn `server.js`. Viết lại `CMD ["server.js"]` trong cùng file.

`--entrypoint` trên `docker run` thay executable, kể cả khi Dockerfile đã khóa. Phần sau tên image vẫn là argument của executable mới.

<div class="net-flow">
<div class="net-step" style="--layer:#dbeafe"><b>CMD</b><span>lệnh mặc định, cả dòng bị thay</span></div>
<span class="net-arrow" aria-hidden="true">→</span>
<div class="net-step" style="--layer:#e0e7ff"><b>ENTRYPOINT exec</b><span>executable giữ nguyên</span></div>
<span class="net-arrow" aria-hidden="true">→</span>
<div class="net-step" style="--layer:#dcfce7"><b>Argument</b><span>CMD mặc định, hoặc chữ sau tên image</span></div>
</div>

## Lệnh và code mẫu

Dùng lại `server.js` ở `$HOME/devops-lab/docker/app`.

Chỉ `CMD`. Lệnh sau tên image thay cả dòng:

```dockerfile
# syntax=docker/dockerfile:1

FROM node:24-alpine
WORKDIR /app
COPY server.js ./
USER node
CMD ["node", "server.js"]
```

```bash
cd "$HOME/devops-lab/docker/app"
docker build -t devops-lab-cmd:1 .
docker run --rm devops-lab-cmd:1 node -e "console.log('thay ca CMD')"
```

Container in `thay ca CMD` rồi thoát. Không có process `server.js`.

Khóa `node`, để `server.js` là argument mặc định:

```dockerfile
# syntax=docker/dockerfile:1

FROM node:24-alpine
WORKDIR /app
COPY server.js ./
USER node
ENTRYPOINT ["node"]
CMD ["server.js"]
```

```bash
docker build -t devops-lab-entry:1 .
docker run --name lab-entry --detach --rm --publish 3000:3000 devops-lab-entry:1
curl -sS http://127.0.0.1:3000/healthz
docker stop lab-entry
docker run --rm devops-lab-entry:1 -e "console.log('noi sau node')"
docker run --rm --entrypoint echo devops-lab-entry:1 hello
```

Lần `docker run` không argument vẫn là server, `/healthz` in `ok`. Lần có `-e ...` chạy `node -e ...` và in `noi sau node`. Chữ `-e` đứng sau tên image, nên nó là argument của `node`, không phải cờ môi trường của Docker. Cờ của Docker, kể cả `--entrypoint`, đứng trước tên image. `--entrypoint echo` chạy `echo hello`.

Dạng shell, để thấy argument bị bỏ. Đừng dùng dạng này cho app:

```dockerfile
ENTRYPOINT node -e "console.log('shell')"
```

`docker run img them-chu` vẫn chỉ in `shell`. Chữ `them-chu` không thành argument.

## Thực hành

1. Build `devops-lab-cmd:1` với chỉ `CMD ["node", "server.js"]`.
2. `docker run --rm devops-lab-cmd:1 node -e "console.log('thay ca CMD')"`.
3. Đổi Dockerfile sang `ENTRYPOINT ["node"]` và `CMD ["server.js"]`, build `devops-lab-entry:1`.
4. Chạy detached, publish `3000:3000`, gọi `/healthz`, rồi `docker stop`.
5. `docker run --rm devops-lab-entry:1 -e "console.log('noi sau node')"`.
6. `docker run --rm --entrypoint echo devops-lab-entry:1 hello`.

## Kết quả mong đợi

- Bước 2 in `thay ca CMD`. Container không giữ server.
- Bước 4: `curl` in `ok`. `docker top` thấy `node server.js`.
- Bước 5 in `noi sau node`.
- Bước 6 in `hello`.

## Checklist

- [ ] Nhìn argument sau tên image: với `CMD`, cả lệnh mặc định biến mất.
- [ ] Với `ENTRYPOINT` dạng mảng, executable còn, argument được nối sau.
- [ ] Viết cả hai chỉ thị bằng mảng JSON.
- [ ] Sau `ENTRYPOINT`, viết lại `CMD` nếu image gốc có lệnh mặc định cần giữ.
- [ ] `--entrypoint` khi cần đổi executable một lần chạy.

## Thẻ ôn

### docker-entrypoint-cmd
kind: distinguish
cue: Chữ viết sau tên image thay cả `CMD`. `docker run img node -e ...` không chạy `CMD ["node", "server.js"]` nữa.

Q: Dockerfile chỉ có `CMD ["node", "server.js"]`. Bạn chạy `docker run img node -e "console.log(1)"` để in một dòng rồi vẫn mong server nghe port. Process nào thực sự chạy?

A: Argument sau tên image thay toàn bộ `CMD`. Container chạy `node -e "console.log(1)"`, in một dòng rồi thoát. `server.js` không được gọi. Muốn server, bỏ phần sau tên image, hoặc đưa lệnh start vào `ENTRYPOINT` nếu phần sau tên image chỉ là tham số.

### docker-entrypoint-append
kind: distinguish
cue: `ENTRYPOINT ["node"]` giữ `node`. `CMD ["server.js"]` là argument mặc định. `docker run img -e ...` thành `node -e ...`.

Q: Dockerfile có `ENTRYPOINT ["node"]` và `CMD ["server.js"]`. `docker run img` chạy gì, và `docker run img -e "console.log(1)"` chạy gì?

A: Không có chữ sau tên image thì argument là `CMD`, lệnh là `node server.js`. Có chữ sau tên image thì `CMD` bị thay, chữ đó nối sau entrypoint, lệnh là `node -e "console.log(1)"`. Executable `node` vẫn còn. Cả hai chỉ thị phải là mảng JSON. `CMD` dạng chuỗi sẽ thành `/bin/sh -c ...` nối sau `node`.

### docker-entrypoint-shell
kind: trap
cue: `ENTRYPOINT node server.js` không có ngoặc vuông đi qua shell. Argument lúc `docker run` và `CMD` đều bị bỏ.

Q: Dockerfile ghi `ENTRYPOINT node server.js`, không phải mảng JSON. Bạn thêm `CMD ["--watch"]` và lúc chạy gõ `docker run img --debug`. Process chính là gì, và hai argument đó có tới `node` không?

A: Dạng shell chạy `/bin/sh -c "node server.js"`. Docs bỏ qua `CMD` và bỏ qua argument trên `docker run` với dạng này. `--watch` và `--debug` không tới `node`. PID 1 là shell, `docker stop` gửi signal tới shell. Viết `ENTRYPOINT ["node", "server.js"]` khi không cần argument thêm, hoặc tách `ENTRYPOINT ["node"]` và `CMD ["server.js"]`.

### docker-entrypoint-flag
kind: command
cue: `--entrypoint` đứng trước tên image và thay executable đã khóa trong Dockerfile.

Q: Image khóa `ENTRYPOINT ["node"]`. Một lần debug bạn cần chạy `echo` trong cùng image, không sửa Dockerfile. Lệnh nào làm việc đó?

A: `docker run --rm --entrypoint echo img hello` chạy `echo hello`. Cờ `--entrypoint` đứng trước tên image và thay executable của lần chạy này. Chữ sau tên image vẫn là argument của executable mới. Lần chạy sau, không có cờ, thì entrypoint trong image trở lại.

### docker-entrypoint-reset
kind: trap
cue: Thêm `ENTRYPOINT` là `CMD` của image gốc bị xóa. Viết lại `CMD` trong cùng Dockerfile nếu vẫn cần argument mặc định.

Q: Image `node` gốc đã có lệnh mặc định. Dockerfile của bạn chỉ thêm `ENTRYPOINT ["node"]`, không có dòng `CMD`. `docker run` không argument sẽ chạy gì?

A: Khai `ENTRYPOINT` xóa `CMD` kế thừa từ image `FROM`. Không viết `CMD` mới thì không có argument mặc định, `node` khởi động rồi thoát vì thiếu script. Viết `CMD ["server.js"]` trong cùng Dockerfile, dạng mảng JSON, nếu `server.js` vẫn là lệnh muốn chạy khi người dùng không gõ thêm gì.

## Lỗi thường gặp

**`ENTRYPOINT node server.js` vì đỡ phải gõ ngoặc.** Argument lúc chạy biến mất, signal dừng ở shell. Dùng mảng JSON.

**Thêm `ENTRYPOINT` mà không viết lại `CMD`.** Lệnh mặc định của image gốc mất. `docker run` không argument không còn chạy app.

**`CMD node server.js` dạng chuỗi cạnh `ENTRYPOINT ["node"]`.** Argument mặc định thành một lần gọi shell nối sau `node`, không phải `node server.js`.

**Sửa lệnh start bằng cách `docker commit` một container đã `exec`.** Lệnh start đọc từ Dockerfile. Sửa `CMD` hoặc `ENTRYPOINT` trong file rồi build lại.

## Nguồn

1. [KodeKloud — Docker for the Absolute Beginner](https://learn.kodekloud.com/learn/courses/docker-training-course-for-the-absolute-beginner) — module Docker Images: Commands vs Entrypoint. Bài viết lại, không chép lab.
2. [CMD](https://docs.docker.com/reference/dockerfile/#cmd) — lệnh mặc định, bị argument của `docker run` thay.
3. [ENTRYPOINT](https://docs.docker.com/reference/dockerfile/#entrypoint) — dạng exec và shell, bảng kết hợp với `CMD`, `--entrypoint`.

## Tiếp theo

[Biến môi trường](./10-env.md)
