[Docker] Dockerfile instructions

Cùng tìm hiểu một vài instructions được sử dụng nhiều trong Dockerfile. Phần này tôi tổng hợp chủ yếu từ Dockerfile best pratice luôn, chỉ có 1 ít đá ké sang Dockerfile reference. Do đó nếu muốn xem chi tiết instruction đó cú pháp và hoạt động chi tiết, hãy vào Dockerfile reference để đọc.

FROM

Image được sử dụng làm basic. Bạn có thể sử dụng nó kết hợp với ARG. Tuy nhiên, các ARG được khai báo trước FROM thì sẽ không được sử dụng trong các bước build tiếp theo, do đó bạn cần khai báo lại:

1
2
3
4
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

Chúng tôi khuyên bạn nên sử dụng các Alpine image vì nó được kiểm soát chặt chẽ hơn, kích thước nhỏ (dưới 5MB) so với các bản Linux đầy đủ.

LABEL

Phần này có thể sử dụng để bổ sung lưu trữ các thông tin, thông tin sở hữu hoặc bất cứ thông tin gì bạn muốn mô tả thêm về docker. Khi sử dụng string, nó cần đặt trong dấu “” và cần escape nếu kí tự đặc biệt:

1
2
3
4
5
6
# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

RUN

RUN thường sử dụng để cài phần mềm hoặc package như bạn cần cài trên ubuntu ấy

RUN sẽ thực thi 1 vài lệnh trên 1 tầng mới ở đầu image hiện tại và commit kết quả. Kết quả được ghi lại và sử dụng ở các bước tiếp theo trong Dockerfile

RUN sẽ có 2 cấu trúc:

  • RUN <command> (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)
  • RUN ["executable", "param1", "param2"] (exec form)
  • (Mặc định thì nó sẽ là shell form)

exec form cũng được sử dụng nhiều, nó cũng có thể sử dụng để chạy shell form:

1
RUN ["/bin/bash", "-c", "echo hello"]

Note: Không giống như shell form, exec form không gọi 1 command shell. Điều đó có nghĩa các xử lý shell thông thường sẽ không diễn ra. Ví dụ RUN [ “echo”, “$HOME” ] sẽ không thay thế biến $HOME. Nếu bạn muốn thực thi shell thì sử dụng shell form hoặc thực thi shell 1 cách trực tiếp, ví dụ RUN [ “sh”, “-c”, “echo $HOME” ]. Khi sử dụng exec for và thực thi shell trực tiếp như shell form, nó là shell làm việc với mở rộng với biến môi trường chứ không phải docker.

Note (Bản gốc): Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, RUN [ “echo”, “$HOME” ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: RUN [ “sh”, “-c”, “echo $HOME” ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.

Cache của RUN không bị tự động vô hiệu trong quá trình build tiếp theo (tức là được sử dụng lại). Cache của lệnh RUN apt-get dist-upgrade -y sẽ được sử dụng lại trong lần build tiếp theo. Cache sẽ vô hiệu hóa khi bạn build lại với tùy chọn --no-cache như docker build --no-cache

APT-GET

Đa số trường hợp sử dụng RUN là để chạy ứng dụng của apt-get. Bởi vì nó để cài đặt các package chạy lệnh RUN apt-get, tuy nhiên cũng có 1 vài chú ý với lệnh này:

  • Tránh RUN apt-get upgradedist-upgrade vì nhiều package trong số các package thiết yếu của image cha không thể nâng cấp bên trong 1 container không có đặc quyền. Nếu 1 package được chứa trong parent image đã out-of-date, liên hệ đội ngũ maintain nó

  • Hãy sử dụng kết hợp RUN apt-get update kết hợp với apt-get install như ví dụ sau:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
    && rm -rf /var/lib/apt/lists/*

    Chạy các lệnh như thực thi trong container. Sử dụng dấu phân cách để tách biệt nhiều lệnh trên nhiều dòng cho dễ đọc và maintain:

    1
    2
    RUN /bin/bash -c 'source $HOME/.bashrc; \
    echo $HOME'

    USING PIPES

Một vài lệnh RUN phụ thuộc vào kết quả output của 1 vài lệnh khác, lúc đó, hãy sử dụng pipe (|)

1
RUN wget -O - https://some.site | wc -l > /number

If you want the command to fail due to an error at any stage in the pipe, prepend set -o pipefail && to ensure that an unexpected error prevents the build from inadvertently succeeding. For example:
1
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

Note: Không hỗ trợ tùy chọn -o pipefail

CMD

The CMD instruction has three forms:

  • CMD ["executable","param1","param2"] (exec form, this is the preferred form)
  • CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
  • CMD command param1 param2 (shell form)

Hai cách đầu thường được sử dụng nhiều, đặc biệt là các số 2. Nó thường kết hợp với ENTRYPOINT và bạn nên đọc ở phần sau.

Lệnh CMD nên được sử dụng để chạy phần mềm được chứa trong image của bạn cùng với 1 vài tham số (ví dự như chạy nodejs: node tenfile, hay php artisan serve trong Laravel ấy =)).

CMD đa số nên được sử dụng dưới dạng :

1
CMD ["executable", "param1", "param2"…]

Ví dụ như apache
1
CMD ["apache2","-DFOREGROUND"]

Trong đa số các trường hợp khác, CMD được sử dụng như 1 sự tích hợp shell, như bash, pythonperl. Ví dụ CMD ["perl", "-de0"], CMD ["python"], or CMD ["php", "-a"]. Sử dụng các lệnh như trên tương đương với việc bạn thực thi docker như docker run -it python. CMD sẽ rất hiếm khi được sử dụng theo cách CMD ["param", "param"] kết hợp với ENTRYPOINT, trừ khi bạn và người dùng đã quá khen thuộc với cách sử dụng ENTRYPOINT hoạt động như thế nào

EXPOSE

1
EXPOSE <port> [<port>/<protocol>...]

Bình thường các Docker container hoạt động độc lập, không chia sẽ cho nhau cái gì, ai biết nhà nấy (MySQL biết MySQL, Nginx biết Nginx, không mở lắng nghe kết nối từ đâu đến). Nhưng như vậy không ổn, chúng cần lắng nghe và kết nối với nhau để tạo được 1 ứng dụng. EXPOSE giúp chúng ta làm việc này.

Lệnh EXPOSE chỉ ra cổng nào mà 1 container lắng nghe kết nối. Do đó bạn nên sử dụng các cổng truyền thống và chung nhất cho ứng dụng của mình. Ví dụ như Apache sẽ EXPOSE cổng 80 trong khi image của MongoDB sẽ sử dụng EXPOSE 27017

1
2
EXPOSE 80/tcp
EXPOSE 80/udp

Để truy cập bên ngoài (như local machine chẳng hạn), bạn có thể sử dụng docker run với với ánh xạ cổng được chỉ định đến cổng mà bạn lựa chọn, với tùy chọn -p.

Đối với các conatianer liên kết với nhau, Docker cung cấp các biến môi trường đối với đường dẫn từ container nhận được trở về container nguồn (ví dụ như biến MYSQL_PORT_3306_TCP).

1
docker run -p 80:80/tcp -p 80:80/udp ...

It functions as a type of documentation between the person who builds the image and the person who runs the container, about which ports are intended to be published.

Giải thích: EXPOSE có thể có hoặc không, container của bạn vẫn chạy và có thể vẫn được expose bình thường với docker-compose. Tuy nhiên hãy thêm nó, vì cũng chẳng tốn công gì, và coi nó như 1 dạng của tài liệu truyền đạt thông tin giữa người xây dựng container và người chạy container, về những cổng nào mà sẽ được publish

ENV

1
2
ENV <key> <value>
ENV <key>=<value> ...

Cho phép phần mềm của bạn dễ dàng chạy, bạn có thể sử dụng ENV cập nhật PATH trong biến môi trường cho những phần mềm được cài trong container. Ví dụ ENV PATH /usr/local/nginx/bin:$PATH để chắc chắn rằng CMD ["nginx"] hoạt động.

Lệnh ENV cũng hữu ích khi cung cấp các biến môi trường cho bạn, như các const trong khi lập trình, hoặc các phiên bản để bạn dễ dàng bảo trì hơn:

1
2
3
4
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

Mỗi dòng ENV tạo 1 ra 1 tầng trung gian mới, tương tự như RUN. Điều đó có nghĩa, thậm chí bạn đã unset biến môi trường ở 1 tầng nào đó trong tương lai, nó vẫn tồn tại ở layer này và giá trị của nó không bị hủy bỏ. Bạn có thể build Dockerfile sau và kiểm tra nó

1
2
3
4
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER

1
2
3
$ docker run --rm test sh -c 'echo $ADMIN_USER'

mark

Để ngăn chặn việc này, hãy xử nó ở chính layer đơn đó bằng ; hoặc &&
1
2
3
4
5
FROM alpine
RUN export ADMIN_USER="mark" \
&& echo $ADMIN_USER > ./mark \
&& unset ADMIN_USER
CMD sh

1
$ docker run --rm test sh -c 'echo $ADMIN_USER'

ADD or COPY

1
2
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] (this form is required for paths containing whitespace)

Cả 2 lệnh này đều thực hiện copy new files, thư mục từ <src> và thêm nó vào thư mục filesystem <dest> trong image.

  • The ADD instruction copies new files, directories or remote file URLs from <src> and adds them to the filesystem of the image at the path <dest>.
  • The COPY instruction copies new files or directories from <src> and adds them to the filesystem of the container at the path <dest>.

Mặc dù nhìn thì ADD và COPY tương tự về chức năng, nhưng nói chung COPY vẫn được ưa chuộng hơn. Đó là bởi vì nó minh bạch nhiều hơn ADD. COPY chỉ hỗ trợ các phép sao chép cơ bản các file local vào trong container trong khi ADD có 1 vài chức năng (như giải nén file tar, remote URL support) cái mà không được minh bạch ngay lập tức. Do đó, cách sử dụng tốt nhất cho ADD là tự động trích xuât tệp tar local vào image như ADD rootfs.tar.xz /

Nếu bạn có nhiều bước trong Dockerfile sử dụng các file khác nhau trong context của bạn, COPY từng file một hơn là copy tất cả trong 1 lần. Điều này để chắc chắn rằng mỗi bước build, cache sẽ bị vô hiệu hóa (buộc các bước này phải chạy lại) nếu các tệp được yêu cầu cụ thể thay đổi (thay vì chạy lại hết mà 1 file có thể không liên quan thay đổi).

Ví dụ:

1
2
3
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/

Kết quả việc vô hiệu hóa cache của RUN sẽ ít hơn bến bạn đặt COPY . /tmp/ trước nó (vì khi thay đổi requirements.txt mới bị vô hiệu hóa cache. Bạn đặt COPY . /tmp/ trước thì thay đổi bất kì file nào đều vô hiệu hóa cache)

Do vấn đề kích thước (size) của image, việc sử dụng ADD để nạp các package từ remote URLs KHÔNG được khuyến khích. Thay vào đó, bạn nên sử dụng curl hoặc wget. Và bằng cách này, bạn có thể xóa file đi sau khi bạn giải nén và sử dụng để không ảnh hưởng đến các layer khác trong image. Ví dụ thay vì như vậy:

1
2
3
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

Thì bạn nên:
1
2
3
4
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all

ENTRYPOINT

ENTRYPOINT có 2 kiểu:

  • ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
  • ENTRYPOINT command param1 param2 (shell form)

Một ENTRYPOINT cho phép bạn cấu hình để thực thi container

Ví dụ start nginx với cổng 80:

1
docker run -i -t --rm -p 80:80 nginx

docker run <image> sẽ được thêm vào sau tất cả các thành phần với exec form ENTRYPOINT và sẽ ghi đè tất cả các thành phần được chỉ định sử dụng CMD

shell form chặn 1 vài CMD hoặc lệnh run với 1 vài tham số nhưng nhược điểm của kiểu ENTRYPOINT này sẽ bắt đầu như là 1 subcommand của /bin/sh -c, cái mà sẽ không có tín hiệu. Điều đó có nghĩa thực thi này sẽ không phải là PID của container 1 và sẽ không nhận được các tín hiệu từ Unix - do đó sự thực thi container này sẽ không nhận một SIGTERM từ docker stop <container>

Chỉ lệnh ENTRYPOINT cuối cùng trong Dockerfile là có hiệu lực.

Exec form ENTRYPOINT example

Bạn có thể sử dụng exec form của ENTRYPOINT để thiết lập lệnh mặc định và các đối số và sau đó sử dụng CMD để thiết lập thêm các tham số có nhiều khả năng thay đổi

1
2
3
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

Và khi bạn chạy container, bạn sẽ nhìn thấy top chỉ là 1 tiến trình:

1
2
3
4
5
6
7
8
9
$ docker run -it --rm --name test  top -H
top - 08:25:00 up 7:27, 0 users, load average: 0.00, 0.01, 0.05
Threads: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffers
KiB Swap: 1441840 total, 0 used, 1441840 free. 1324440 cached Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top

Để xem kết quả nhiều hơn nữa, bạn có thể sử dụng docker exec
1
2
3
4
$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 2.6 0.1 19752 2352 ? Ss+ 08:24 0:00 top -b -H
root 7 0.0 0.1 15572 2164 ? R+ 08:25 0:00 ps aux

Và bạn có thể gửi 1 request top để tắt docker đang sử dụng đi docker stop test.

Một ví dụ khác sử dụng ENTRYPOINT để chạy Apache:

1
2
3
4
5
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

Nếu bạn cần viết 1 script khởi động của 1 sự thực thi đơn, bạn có thể chắc chắn rằng lệnh cuối cùng nhận được tín hiệu Unix bằng cách sử dụng execgosu commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"

if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi

exec gosu postgres "$@"
fi

exec "$@"

Cuối cùng, nếu bạn cần thêm 1 số sự dọn dẹp (hoặc kết nối với container khác) khi shutdown hoặc đang có nhiều hơn 1 sự thực thi, bạn có thể cần chắc chắn rằng script ENTRYPOINT nhận Unix signals, vượt qua chúng và sau đó thực hiện 1 số công việc khác:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too

# USE the trap if you need to also do manual cleanup after the service is stopped,
# or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM

# start service in background here
/usr/sbin/apachectl start

echo "[hit enter key to exit] or run 'docker stop <container>'"
read

# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"

If you run this image with docker run -it --rm -p 80:80 --name test apache, you can then examine the container’s processes with docker exec, or docker top, and then ask the script to stop Apache:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.0 4448 692 ? Ss+ 00:42 0:00 /bin/sh /run.sh 123 cmd cmd2
root 19 0.0 0.2 71304 4440 ? Ss 00:42 0:00 /usr/sbin/apache2 -k start
www-data 20 0.2 0.2 360468 6004 ? Sl 00:42 0:00 /usr/sbin/apache2 -k start
www-data 21 0.2 0.2 360468 6000 ? Sl 00:42 0:00 /usr/sbin/apache2 -k start
root 81 0.0 0.1 15572 2140 ? R+ 00:44 0:00 ps aux
$ docker top test
PID USER COMMAND
10035 root {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054 root /usr/sbin/apache2 -k start
10055 33 /usr/sbin/apache2 -k start
10056 33 /usr/sbin/apache2 -k start
$ /usr/bin/time docker stop test
test
real 0m 0.27s
user 0m 0.03s
sys 0m 0.03s

Một vài chú ý tương tự RUN

Note: The exec form is parsed as a JSON array, which means that you must use double-quotes (“) around words not single-quotes (‘).

Note: Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, ENTRYPOINT [ “echo”, “$HOME” ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: ENTRYPOINT [ “sh”, “-c”, “echo $HOME” ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.

Shell form ENTRYPOINT example

Thực thi các lệnh shell

Understand how CMD and ENTRYPOINT interact

Cả CMD và ENTRYPOINT đều được sử dụng để định nghĩa ra các lệnh sẽ được thực thi để chạy container. Tuy nhiên có 1 vài rules kết hợp 2 lệnh này bạn cần biết:

  • Dockerfile nên chỉ có 1 lệnh CMD hoặc ENTRYPOINT
  • ENTRYPOINT nên được xác định khi sử dụng container như là một thực thi.
  • CMD nên được sử dụng như 1 cách định nghĩa các đối số mặc định của 1 ENTRYPOINT hoặc là để thực thi 1 ad-hoc comamnd trong 1 container
  • CMD sẽ bị ghi đè khi chạy container với các đối số thay thế

Bảng sau sẽ chỉ ra lệnh được thực thi thực sự cho mỗi sự kết hợp ENTRYPOINT / CMD

Chú ý: Nếu CMD được xác định từ 1 base image, setting ENTRYPOINT sẽ reset CMD về 1 giá trị trống. Trong trường hợp này, CMD cần phải được xác định trong current image để có giá trị

VOLUME

The VOLUME instruction creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers

Lệnh này để tạo 1 điểm liên kết (mount point) với tên rõ ràng và đánh dấu nó như là nơi lưu trữ volumes được mount ra ngoài từ host lưu trữ hoặc containers khác.

Giá trị của nó có thể là mảng JSON VOLUME ["/var/log/"] hoặc plain string với nhiều đối số như VOLUME /var/log or VOLUME /var/log /var/db

Lệnh docker run khởi tạo 1 volume được tạo mới với 1 vài dữ liệu đã tồn tại trong location được chỉ định từ base image. Ví dụ:

1
2
3
4
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

Kết quả Dockerfile trong 1 image khi chạy docker run tạo ra 1 điểm liên kết tại thư mục /myvol và copy greeting file vào trong volumn được tạo mới đó.

Notes about specifying volumes

  • Volumes on Windows-based containers: Khi sử dụng Windows-based containers, đích của 1 volumn trong 1 container phải là 1 trong:
    • a non-existing or empty directory
    • a drive other than C:
  • Changing the volume from within the Dockerfile: Nếu 1 vài bước build thay đổi dữ liệu bên trong volumne sau khi nó được khai báo, các thay đổi này sẽ bị loại bỏ
    (Hiểu là đã VOLUMNE rồi mà lệnh sau trong Dockerfile thay đổi file trong thư mục đó nữa thì không được cập nhật đâu :D => chưa thử)
  • JSON formatting: Khi sử dụng JSON array bạn phải sử dụng dấu nháy kép “ thay vì nháy đơn ‘
  • The host directory is declared at container run-timer: Thư mục host (mountpoint) về bản chất, phụ thuộc vào host (host-dependent). Điều này để bảo vệ tính di động của image, vì một thư mục của host không thể đảm bảo rằng nó đã có trên tất cả các hosts. Vì lí do này, bạn không thể mount 1 thư mục host từ trong Dockerfile. Lệnh VOLUME không hỗ trợ việc chỉ định một tham số host-dir. Bạn phải chỉ định mountpoint được bạn tạo ra hoặc khi chạy container (tôi đang hiểu phần này là các thư mục có sẵn của host thì không mount được, ví dụ như không mount thư mục /var từ container ubuntu sang được vì nó thuộc về host, chỉ mount những file, thư mục mình tạo ra thôi :D)

USER

1
2
USER <user>[:<group>] or
USER <UID>[:<GID>]

Lệnh này tạo tập người dùng (hoặc UID) và nhóm người dùng (GID) để sử dụng khi chạy image cho các lệnh RUN, CMD và ENTRYPOINT trong Dockerfile

Warning: When the user doesn’t have a primary group then the image (or the next instructions) will be run with the root group.

On Windows, the user must be created first if it’s not a built-in account. This can be done with the net user command called as part of a Dockerfile.

1
2
3
4
5
FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick

WORKDIR

1
WORKDIR /path/to/workdir

Lệnh này chỉ ra tập các thư mục để làm việc với các lệnh RUN, CMD, ENTRYPOINT, COPY và ADD trong Dockerfile. Nếu WORKDIR không tồn tại, nó sẽ được tạo thậm chí nếu nó không được sử dụng trong 1 các bước của Dockerfile.

Lệnh WORKDIR có thể sử dụng nhiều lần trong 1 Dockerfile. Nếu là một đường dẫn tuyệt đối, nó sẽ có liên quan đến đường dẫn của lệnh WORKDIR trước đó

Ví dụ:

1
2
3
4
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

Thì lệnh pwd sẽ được thực thi trong thư mục /a/b/c

Lệnh WORKDIR có thể phân giải được các biến môi trường sử dụng bởi ENV

1
2
3
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

ARG

1
ARG <name>[=<default value>]

Sử dụng để định nghĩa các biến mà người dùng có thể truyền động khi build với lệnh docker build sử dụng flag –build-arg =. Hoặc có thể sử dụng trong Dockerfile

1
2
3
4
FROM busybox
ARG user1
ARG buildno
...

Tổng kết

  • FROM chỉ ra chúng ta base image chúng ta sử dụng, LABEL bổ sung các thông tin liên quan đến bản quyền, bổ sung thông tin
  • RUN được sử dụng kết hợp apt-get, thường được sử dụng cài đặt package. Ngoài ra nó cũng được sử dụng chạy các bash hoặc các php, go, python script
  • ENV sử dụng để gán các biến CONST như lập trình, như vậy code của bạn sẽ dễ dàng theo dõi hơn.
  • CMD và ENTRYPOINT được sử dụng để chạy các lệnh bật container của bạn (như bật Apache2, nginx, nodejs …)
  • Một container được tạo ra sẽ độc lập với các container khác. Do đó cần có 1 số tham số để giao tiếp với nhau
    • EXPOSE: mở 1 cổng lắng nghe kết nối ra bên ngoài
    • VOLUME: chia sẻ file, thư mục ra ngoài host hoặc các container khác
    • ADD, COPY: copy thư mục từ host vào container
  • RUN, ENTRYPOINT, CMD có 2 dạng và exec form và shell form. (CMD có thêm 1 dạng nữa chỉ nhận tham số từ ENTRYPOINT). Chúng ta thử xem 2 form này có gì khác biệt nhé:

Exec form rộng hơn shell form, trỏ được thư mục nào để thực thi lệnh.

Shell form lệnh sẽ chạy trong 1 shell, cái mà được định nghĩa là /bin/sh -c trên Linux hoặc cmd /S /C trên Windows

1
2
3
# Shell form
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

1
2
3
4
5
6
# Exec form
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"

Shell cũng có thể sử dụng từ exec form
1
RUN ["/bin/bash", "-c", "echo hello"]

Và một note nữa là dùng exec form RUN [ "echo", "$HOME" ]

Note: Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, ENTRYPOINT [ “echo”, “$HOME” ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: ENTRYPOINT [ “sh”, “-c”, “echo $HOME” ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.

Note: Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, CMD [ “echo”, “$HOME” ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: CMD [ “sh”, “-c”, “echo $HOME” ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.

Note: Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, RUN [ “echo”, “$HOME” ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: RUN [ “sh”, “-c”, “echo $HOME” ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.

  • Có 1 sự kết hợp CMDENTRYPOINT rất hay nhé (Xem lại ảnh :D). Và trong 1 Dockerfile, chỉ nên có 1 CMDENTRYPOINT nhé (note tại Understand how CMD and ENTRYPOINT interact)
  • Một ví dụ Dockerfile cho Apache2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    FROM ubuntu:16.04
    # Note: MAINTAINER đã không còn được sử dụng nữa nhé
    MAINTAINER James Turnbull <james@example.com>
    ENV REFRESHED_AT 2016-06-01

    RUN apt-get -yqq update
    RUN apt-get -yqq install apache2

    VOLUME [ "/var/www/html" ]

    WORKDIR /var/www/html

    ENV APACHE_RUN_USER www-data
    ENV APACHE_RUN_GROUP www-data
    ENV APACHE_LOG_DIR /var/log/apache2
    ENV APACHE_PID_FILE /var/run/apache2.pid
    ENV APACHE_RUN_DIR /var/run/apache2
    ENV APACHE_LOCK_DIR /var/lock/apache2

    RUN mkdir -p $APACHE_RUN_DIR $APACHE_LOCK_DIR $APACHE_LOG_DIR

    EXPOSE 80

    ENTRYPOINT [ "/usr/sbin/apache2" ]
    CMD ["-D", "FOREGROUND"]

    Tài liệu tham khảo:

  • Dockerfile best paractices
  • Dockerfile reference
Author

Ming

Posted on

2020-04-12

Updated on

2021-04-10

Licensed under

Comments