สรุป Best practices for writing Dockerfiles - SilverSky9/DevToolNo1 GitHub Wiki

General guidelines and recommendations

Create ephemeral containers การกำหนด image โดยใช้ Dockerfile นั้นควรสร้างเป็นคอนเทนเนอร์ชั่วคราวที่สุดเท่าที่จะทำได้ คำว่า ชั่วคราวในที่นี้ หมายถึงการที่คอนเทนเนอร์ สามารถหยุด และโดนทำลาย หลังจากนั้นมีการสร้างใหม่ขึ้นและมีการแทนที่อันเก่า ด้วยการตั้งค่าที่เราได้กำหนดขึ้น Understand build context เมื่อเราใช้คำสั่ง docker build เราจะเรียกการทำงานลักษณะนี้ว่า ‘build context’ เราสามารถเปลี่ยนตำแหน่งที่อยู่ไฟล์จากเดิมค่าเริ่มต้นเป็นตำแน่งใหม่ที่เราต้องการโดยใช้แฟล็กไฟล์ (-f) ตัวอย่างคำสั่ง สร้างที่อยู่ใหม่ และทำการเข้าไปยังที่อยู่นั้น จากนั้นเขียนคำว่า “hello” ในไฟล์ชื่อ hello และมีการสร้าง Dockerfile ที่รัน cat. มีการสร้าง image เกิดขึ้น

mkdir myproject && cd myproject
$ echo "hello" > hello
$ echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfile
$ docker build -t helloapp:v1 .

ย้าย Dockerfile และ hello ไปยังที่อยู่ที่ได้แยกไว้จากนั้นมีการสร้าง image เป็นเวอร์ชันที่สอง โดยใช้ -f เพื่อชี้ไปยังตำแหน่งที่ต้องการจัดเก็บ

mkdir -p dockerfiles context
$ mv Dockerfile dockerfiles && mv hello context
$ docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context

การรวมไฟล์ที่ไม่จำเป็นในการสร้าง image ทำให้มีการสร้างขนาดใหญ่เกิดขึ้นส่งผลให้เวลาในการสร้างเพิ่มมากขึ้น ในการดูว่าการสร้างมีขนาดเท่าไหร่สังเกตุจากข้อความด้านล่างนี้

Sending build context to Docker daemon  187.8MB

Sending build context to Docker daemon 187.8MB Pipe Dockerfile through stdin Docker มีความสามารถการสร้างimages โดยใช้วิธี piping Dockerfile ผ่าน stdin ด้วย local หรือ remote build context การทำลักษณะนี้มีข้อดีคือเป็นการสร้างแบบครั้งเดียวโดยไม่ต้องเขียนDockerfile ลงในดิสก์

ตัวอย่างการเขียน

echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -

docker build -<<EOF
FROM busybox
RUN echo "hello world"
EOF

Build an image using a Dockerfile from stdin, without sending build context วิธีนี้จะเป็นการสร้างโดยจะไม่มีการส่งไฟล์เพิ่มเติมในลักษณะของ build context ไป เราจะใช้เครื่องหมาย ยัติภังค์ (-) ในตำแหน่งของ path และคำสั่งที่ให้ Docker อ่านคำสั่งการสร้างจาก stdinคือ

docker build [OPTIONS] -

คำสั่งการสร้าง image โดยใช้ Dockerfileผ่าน stdin โดย จะไม่มีการส่งไฟล์ในลักษณะของ build context ไปยัง daemon

docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF

การสร้าง image ในลักษณะนี้มีข้อดีคือสามารถใช้ได้กับสถานการณ์ ที่ Dockerfile ไม่ต้องคัดลอกไฟล์ไปยัง image และต้องการปรับปรุงความเร็วในการสร้าง เนื่องจากไม่มีไฟล์ใดส่งไปยัง daemon

Build from a local build context, using a Dockerfile from stdin ใช้ syntax -f หรือ --file เพื่อระบุ Dockerfile ที่จะใช้ โดยใช้ยัติภังค์
(-) เป็นชื่อไฟล์เพื่อสั่งให้ Docker อ่าน Dockerfile จาก stdin:

docker build [OPTIONS] -f- PATH

ตัวอย่าง ใช้ตำแหน่งที่อยู่ปัจจุบัน (.) ในการสร้าง context และสร้าง image โดยใช้ Dockerfile ผ่าน stdin

# create a directory to work in
mkdir example
cd example

# create an example file
touch somefile.txt

# build an image using the current directory as context, and a Dockerfile passed through stdin
docker build -t myimage:latest -f- . <<EOF
FROM busybox
COPY somefile.txt ./
RUN cat /somefile.txt
EOF

Build from a remote build context, using a Dockerfile from stdin

ใช้ syntax -f หรือ --file เพื่อระบุ Dockerfile ที่จะใช้ โดยใช้ยัติภังค์
(-) เป็นชื่อไฟล์เพื่อสั่งให้ Docker อ่าน Dockerfile จาก stdin:

docker build [OPTIONS] -f- PATH

เราจะใช้ syntax นี้ในกรณีที่ต้องการสร้าง image จาก repository ที่ไม่มี Dockerfile หรือในกรณีที่ต้องการสร้าง custom Dockerfile โดยที่ไม่ต้องการ fork repository นั้นๆ ตัวอย่าง เป็นการสร้าง image ที่ใช้ Dockerfile จาก stdin โดยเพิ่มไฟล์ hello.c จาก repository บน GitHub ที่มีชื่อว่า “hello-world”

docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c ./
EOF

Exclude with .dockerignore หากต้องการแยกไฟล์ที่ไม่เกี่ยวข้องกับการสร้างเราจะใช้ .dockerignore โดยไฟล์นี้จะคล้าย .gitignore files Use multi-stage builds การสร้างแบบหลายขั้นตอนช่วยให้ลดขนาดของ image สุดท้ายโดยที่เราไม่จำเป็นต้องลดจำนวนเลเยอร์และไฟล์เลย นั้นเป็นเพราะว่า image ถูกสร้างขึ้นมาในขั้นตอนสุดท้ายในกระบวนการสร้าง เราจึงสามารถย่อขนาดเลเยอร์ของ image ได้โดยการใช้ leveraging build cache ตัวอย่าง หากเราต้องการสร้าง image ที่ประกอบไปด้วยหลายเลเยอร์ เราสามารถเรียงจาก การเปลี่ยนแปลงน้อยไปมาก Don’t install unnecessary packages เพื่อลดความซับซ้อน ลดขนาดไฟล์ ลดจำนวน dependencies ลดเวลาในการสร้าง ต้องหลีกเลี่ยงการติดตั้งแพ็คเกจที่ไม่จำเป็น

Decouple applications การแยกแอปพลิเคชันออกเป็นหลายคอนเทนเนอร์ช่วยให้นำคอนเทนเนอร์กลับมาใช้ใหม่ได้ง่ายขึ้น ควรจำกัดคอนเทนเนอร์แต่ละคอนเทนเนอร์ไว้ที่กระบวนการเดียว ควรทำให้คอนเทนเนอร์ให้สะอาดมากที่สุด หากมีคอนเทนเนอร์ใดที่ต้องติดต่อสื่สารกันควรใช้ Docker container networks เพื่อเช็คให้แน่ใจว่าแต่ละคอนเทนเนอร์สามารถสื่อสารกันได้

Minimize the number of layers ควรลดจำนวนเลเยอร์ใน image ให้น้อยเท่าที่จะทำได้เพื่อเพิ่มประสิทธิภาพในการทำงาน และเราสามารถเพิ่มฟีเจอร์ต่างๆ ต่อไปนี้เพื่อ ลดข้อจำกัดคือ ใช้เฉพาะคำสั่ง RUN, COPY, ADD เพื่อสร้างเลเยอร์และใช้คำสั่งอื่นๆ เพื่อสร้าง image ชั่วคราวและไม่ควรเพิ่มขนาดของการสร้าง หากเป็นไปได้ ให้ใช้การสร้างแบบหลายขั้นตอน และคัดลอกเฉพาะสิ่งที่ต้องการลงใน image สุดท้ายเท่านั้น Sort multi-line arguments ควรจัดเรียงอาร์กิวเมนต์หลายบรรทัดโดยเรียงตามตัวอักษรและตัวเลข เพื่อช่วยหลีกเลี่ยงวามซ้ำซ้อนของแพ็คเกจและทำให้รายการอัปเดตง่ายขึ้นมาก และหากมีการ PRs ให้อ่านและ review ได้ง่ายขึ้นมากยิ่งขึ้น การใช้การเว้นวรรคก่อนแบ็กสแลช () ก็สามารถนำมาปรับใช้ได้

ตัวอย่าง

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion \
  && rm -rf /var/lib/apt/lists/*

Leverage build cache เมื่อสร้าง image Docker จะทำงานตาม Dockerfile โดยดำเนินการตามลำดับที่ระบุ เมื่อตรวจสอบแต่ละคำสั่งแล้ว Docker จะค้นหา image ที่มีอยู่ในแคชซึ่งสามารถนำมาใช้ซ้ำได้ แทนที่จะสร้างอิมเมจใหม่ หากไม่ต้องการใช้แคชสามารถใช้ตัวเลือก --no-cache=true บนคำสั่ง build ได้

Dockerfile instructions FROM ถ้าเป็นไปได้ให้ใช้ official Images ที่เป็นปัจจุบันเป็นพื้นฐานสําหรับ Images แนะนําให้ใช้ Alpine Image เนื่องจากมีขนาดเล็ก (ปัจจุบันมีขนาดต่ํากว่า 6 MB) ในขณะที่ยังเป็นแบบ full Linux distribution

LABEL สามารถเพิ่ม lable ให้กับ Image ได้เพื่อช่วยจัดระเบียบ Images ของโปรเจค, บันทึกข้อมูลใบอนุญาต เพื่อช่วยในระบบอัตโนมัติ หรือด้วยเหตุผลอื่นๆ สำหรับแต่ละ lable ให้เพิ่มบรรทัดที่ขึ้นต้นด้วย LABEL และตามด้วย key-value อย่างน้อยหนึ่งคู่ ตัวอย่างต่อไปนี้แสดงรูปแบบต่างๆ ที่รองรับ

Strings ที่มีช่องว่างต้องยกมาหรือเว้นวรรค อักขระอยู่ในเครื่องหมายคำพูดภายใน (") จะต้องถูกยกเว้นด้วย

# 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=""

Image สามารถมี label ได้มากกว่าหนึ่ง ก่อน Docker 1.10 แนะนําให้รวม lables ทั้งหมดไว้ในคําสั่ง LABEL เดียวเพื่อป้องกันไม่ให้มีการสร้าง extra layers สิ่งนี้ไม่จําเป็นอีกต่อไป แต่การรวม lables ยังได้รับการสนับสนุน

# Set multiple labels on one line
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

จากเนื้อหาข้างต้น สามารถเขียนเป็นแบบนี้ก็ได้

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
  	com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"

RUN แยกคำสั่ง RUN ที่ยาว หรือซับซ้อนออกเป็นหลายบรรทัดโดยคั่นด้วย backslashes เพื่อทำให้ Dockerfile อ่านง่ายขึ้น เข้าใจได้ง่าย และสามารถแก้ไขได้ง่าย apt-get น่าจะเป็นกรณีการใช้งานทั่วไปสำหรับ RUN คือแอปพลิเคชันของ apt-get เนื่องจากมันติดตั้งแพ็คเกจ คำสั่ง RUN apt-get จึงมี gotchas หลายตัวที่ต้องระวัง รวม RUN apt-get update เข้ากับ apt-get install ในคำสั่ง RUN เดียวกันเสมอ ตัวอย่างเช่น:

RUN apt-get update && apt-get install -y \
	package-bar \
	package-baz \
	package-foo  \
	&& rm -rf /var/lib/apt/lists/*

การใช้ apt-get update เพียงอย่างเดียวในคำสั่ง RUN ทำให้เกิดปัญหาในการแคช และภายหลัง apt-get install เกิดการล้มเหลว ตัวอย่างเช่น สมมติว่ามี Dockerfile:

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl

หลังจากสร้าง image แล้ว เลเยอร์ทั้งหมดจะอยู่ในแคชของ Docker สมมติว่าทำการแก้ไข apt-get install ในภายหลังโดยเพิ่ม extra package:

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl nginx

Docker เห็นว่าคำสั่งเริ่มต้นและคำสั่งที่แก้ไขนั้นเหมือนกันและนำแคชกลับมาใช้ใหม่จากขั้นตอนก่อนหน้า ด้วยเหตุนี้ apt-get update จึงไม่ทำงานเนื่องจาก build ใช้เวอร์ชันแคช เนื่องจากไม่มีการรัน apt-get update การ build จึงอาจได้รับแพ็กเกจ curl และ nginx เวอร์ชันที่ล้าสมัยได้ การใช้ RUN apt-get update && apt-get install -y ช่วยให้ Dockerfile ของคุณติดตั้งแพ็คเกจเวอร์ชันล่าสุดโดยไม่มีการเข้ารหัสเพิ่มเติมหรือการแทรกแซงด้วย manual เทคนิคนี้เรียกว่า "cache busting" ยังสามารถดำเนินการป้องกันแคชได้โดยการระบุเวอร์ชันของแพ็คเกจ สิ่งนี้เรียกว่าการตรึงเวอร์ชัน ตัวอย่างเช่น:

RUN apt-get update && apt-get install -y \
	package-bar \
	package-baz \
	package-foo=1.3.*

การตรึงเวอร์ชันบังคับให้บิลด์ดึงข้อมูลเวอร์ชันใดเวอร์ชันหนึ่งโดยไม่คำนึงถึงสิ่งที่อยู่ในแคช เทคนิคนี้ยังช่วยลดความล้มเหลวเนื่องจากการเปลี่ยนแปลงที่ไม่คาดคิดในแพ็คเกจที่จำเป็น ด้านล่างนี้เป็นคำสั่ง RUN ที่มีรูปแบบที่ดี ซึ่งแสดงให้เห็นคำแนะนำ apt-get ทั้งหมด

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/*

อาร์กิวเมนต์ s3cmd ระบุเวอร์ชัน 1.1.* หากก่อนหน้านี้อิมเมจใช้เวอร์ชันเก่า การระบุเวอร์ชันใหม่จะทำให้แคชเสียหาย การใช้ apt-get update และทำให้แน่ใจได้ว่ามีการติดตั้งเวอร์ชันใหม่ การแสดงแพ็คเกจในแต่ละบรรทัดสามารถป้องกันข้อผิดพลาดในการทำซ้ำแพ็คเกจได้ นอกจากนี้เมื่อ clean apt cache โดยการลบ /var/lib/apt/lists มันจะลดขนาดภาพลง เนื่องจาก apt cache ไม่ได้ถูกเก็บไว้ในเลเยอร์ เนื่องจากคำสั่ง RUN เริ่มต้นด้วย apt-get update แพ็คเกจแคชจะถูกรีเฟรชก่อนการ apt-get install เสมอ Official Debian and Ubuntu images จะเรียกใช้ apt-get clean โดยอัตโนมัติ ดังนั้นจึงไม่จำเป็นต้องเรียกใช้อีกครั้ง Using pipes คำสั่ง RUN บางคำสั่งขึ้นอยู่กับความสามารถในการ pipe เอาต์พุตของคำสั่งหนึ่งไปยังอีกคำสั่งหนึ่ง โดยใช้ pipe character (|) ดังในตัวอย่างต่อไปนี้:

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

Docker executes คำสั่งเหล่านี้โดยใช้ตัวแปล /bin/sh -c ซึ่งประเมินเฉพาะ code ที่ดำเนินการล่าสุดใน pipe เพื่อกำหนดความสำเร็จ ในตัวอย่างข้างต้น ขั้นตอนการ build สำเร็จ และสร้าง image ใหม่ขึ้น ตราบใดที่คำสั่ง wc -l สำเร็จ แม้ว่าคำสั่ง wget จะล้มเหลวก็ตาม หากต้องการให้คำสั่ง fail เนื่องจากเกิด error ในขั้นตอนใดๆ ใน pipe ให้เติม set -o pipefail && ไว้ข้างหน้าเพื่อให้แน่ใจว่าข้อผิดพลาดที่ไม่คาดคิดจะป้องกันไม่ให้ build ทำสำเร็จโดยไม่ได้ตั้งใจ ตัวอย่างเช่น:

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

Not all shells support the -o pipefail option. กรณีเช่น dash บน image ที่ใช้ Debian ให้พิจารณาใช้รูปแบบ exec ของ RUN เพื่อเลือกเชลล์ที่สนับสนุนตัวเลือก pipefail อย่างชัดเจน ตัวอย่างเช่น:

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

CMD

  • ควรใช้คำสั่ง CMD เพื่อ run image ร่วมกับอาร์กิวเมนต์ใดๆ CMD ควรใช้ในรูปแบบของ CMD ["executable", "param1", "param2"…] ซึ่งรูปแบบของคำสั่งสำหรับ image ของแต่ละ service ก็จะต่างกันออกไปตามแต่ละ service เช่น เช่น Apache และ Rails จะต้อง เรียกใช้โดย CMD ["apache2","-DFOREGROUND"]

  • ส่วนใหญ่ CMD ควรได้รับ interactive shell เช่น bash, python และ Perl เช่น CMD ["perl", "-de0"], CMD ["python"] หรือ CMD ["php", "-a"] การใช้แบบนี้หมายความว่าเมื่อมีการเรียกใช้งานบางอย่าง เช่น docker run -it python คุณจะเข้าสู่เชลล์ที่ใช้งานได้และพร้อมใช้งาน

  • ไม่ควรใช้ CMD ในลักษณะของ CMD ["param", "param"] ร่วมกับ ENTRYPOINT เว้นแต่จะคุ้นเคยกับวิธีการทำงานของ ENTRYPOINT เป็นอย่างดี

EXPOSE

คำสั่ง EXPOSE ใช้ระบุพอร์ตที่ container จัดการการเชื่อมต่อ

  • ควรใช้ common port สำหรับแอปพลิเคชัน เช่น image ที่มีเว็บเซิร์ฟเวอร์ Apache จะใช้ EXPOSE 80 ในขณะที่ image ที่มี MongoDB จะใช้ EXPOSE 27017 เป็นต้น

ENV

เอาไว้กำหนด environtment variable เพื่อให้ซอฟต์แวร์ใหม่ทำงานได้ง่ายขึ้น สามารถใช้ ENV เพื่ออัปเดต PATH environment variable สำหรับซอฟต์แวร์ที่ติดตั้ง container เช่น

ENV PATH=/usr/local/nginx/bin:$PATH

  • ควรใช้ ENV เพื่อset หมายเลขเวอร์ชันที่ใช้กันทั่วไปได้ เพื่อให้การชนกันของเวอร์ชันนั้นง่ายต่อการแก้ไข เช่น
ENV PG_MAJOR=9.3
ENV PG_VERSION=9.3.4
RUN curl -SL https://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgres && …
ENV PATH=/usr/local/postgres-$PG_MAJOR/bin:$PATH
  • สามารถแยกคำสั่งโดยใช้ ; หรือ && ซึ่งเมื่อใช้ &&จะเป็นการสร้าง condition AND ให้กับการ build docker และการใช้ \ จะช่วยทำให้อ่านง่ายขึ้น

ADD or COPY

ADD ใช้สำหรับ copy ไฟล์, directories หรือ ดาวน์โหลดไฟล์จาก URLs

  • ถึงแม้ว่า ADD และ COPY จะทำงานคล้ายคลึงกัน แต่เราควรใช้ copy มากกว่า เนื่องจาก copy จะทำการคัดลอกเฉพาะข้อมูลหรือคุณสมบัติพื้นฐานของ local files ลงใน container ในขณะที่ add มีคุณสมบัติบางอย่างที่ไม่ชัดเจน
  • หากต้องการใช้ add ควรแตกไฟล์tarไปยัง image
  • เนื่องจากขนาดรูปภาพมีความสำคัญ จึงไม่แนะนำให้ใช้ ADD เพื่อดึงแพ็คเกจจาก URL ระยะไกล ควรใช้ curl หรือ wget แทน ด้วยวิธีนี้ คุณสามารถลบไฟล์ที่คุณไม่ต้องการหลังจากแตกไฟล์แล้ว และไม่จำเป็นต้องเพิ่มเลเยอร์อื่นใน image ตัวอย่างเช่น
RUN mkdir -p /usr/src/things \
    && curl -SL https://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

ENTRYPOINT

  • การใช้ ENTRYPOINT ที่ดีที่สุดคือการset คำสั่งหลักของ image ให้อนุญาตให้เรียกใช้ image และใช้ CMD เป็น default flags

ตัวอย่าง image for the command line tool s3cmd:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

คำสั่งรัน image docker run s3cmd หรือใช้ parameters ที่เหมาะสมในการ execute command docker run s3cmd ls s3://mybucket คำสั่ง ENTRYPOINT ยังสามารถใช้ร่วมกับ helper script เพื่อให้ทำงานในลักษณะเดียวกับคำสั่งด้านบน ตัวอย่าง Postgres Official Image ใช้สคริปต์เป็น ENTRYPOINT:

#!/bin/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 "$@"

Helper script จะถูกcopy ลงใน container และเรียกใช้ผ่าน ENTRYPOINT เมื่อ container start

VOLUME

  • คำสั่ง volume ควรใช้เพื่อแสดง database storage area, configuration storage หรือ files/folders ที่สร้างจาก docker container
  • ควรใช้ volume สำหรับ ส่วนที่เปลี่ยนแปลงได้ และหรือ ส่วนuser-serviceable ของ image

USER

Dockerfile reference for the USER instruction

  • ในกรณีที่ run โดยไม่มีสิทธิ์การเข้าถึง ให้ใช้ USER เพื่อเปลี่ยนเป็น non-root user จากการสร้าง user และ group ใน Dockerfile ด้วยคำสั่ง เช่น RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres

  • ควรหลีกเลี่ยงการติดตั้งหรือใช้ sudo เนื่องจากมี TTY และ signal-forwarding ที่ไม่คาดเดาไม่ได้ซึ่งจะทำให้เกิดปัญหาได้

  • เพื่อลดเลเยอร์และความซับซ้อน ให้หลีกเลี่ยงการสลับ USER ไปมาบ่อยๆ

WORKDIR

  • เพื่อความชัดเจน และน่าเชื่อถือ workdirควรใช้กับ absolute paths เสมอ
  • ควรใช้ workdir แทนคำสั่งจำพวก RUN cd … && do-something ซึ่งอ่าน และแก้ไขได้ยาก

ONBUILD

  • Images ที่สร้างด้วยคำสั่ง onbuild ควรมีแท็กแยก เช่น ruby:1.9-onbuild, ruby:2.0-onbuild ซึ่งจะช่วยให้ไม่เกิดปัญหาเมื่อมีการใช้ ADD หรือ COPY ใน onbuild