Dockerfileのリファレンスを読む (2022) ①

自分だけが使うDockerfileがずっと「かんぜんにりかいした」レベルで、Hadolintに怒られ続けて1年ぐらい経った気がするので、これを機にリファレンスを読み直して再入門しよう、というお話。

新しい発見も結構あったので、備忘録も兼ねて気になった部分だけピックアップ。
長くなりそうなので、まず第1弾。


コメントや命令前のスペース

次の例は間違いではないが、空白は保持されないので “discouraged” (推奨されない)とのこと。

Dockerfile
        # this is a comment-line
    RUN echo hello
RUN echo world

個人的には RUN コマンドの一部をコメントアウトする時はインデントに合わせているが、それぐらいなら問題ないのではないかな。


パーサディレクティブ

個人的新要素。どう処理するかについての特殊なコメントで、拡張や文字環境の差による問題を解決するもの。
以下の注意点がある:

  • 重複できない
  • 他のコメント(未定義ディレクティブを含む)の後に書くと、ただのコメント扱いになる
  • FROM の後でもただのコメント扱い

今のところ、 syntaxescape の2つが定義されている。

syntax

イメージ情報を何のsyntaxで記述するかについて。BuildKitバックエンドが有効な場合に使える。
Docker Buildxを使うと自動で有効になる。もしくは DOCKER_BUILDKIT=1 にする。

Docker公式では「docker/dockerifle:1」が推奨されている。
この部分のバージョニングはsemverなので、breaking changeが入らない限りはmajorは上がらない、ということなのだろう。

これを使うと、Dockerfileの独自拡張ができたり、YAMLとかをDSLに使うオレオレ構文で書けたりする。
ある範囲(エコシステム)に特化したビルドシステムの構築に使われてる印象。

Mockerfile (YAML)
GitHub – r2d4/mockerfile: A proof-of-concept alternative frontend for buildkit
A proof-of-concept alternative frontend for buildkit – GitHub – r2d4/mockerfile: A proof-of-concept alternative frontend for buildkit
github.com
Mockerfile.yaml
#syntax=r2d4/mocker
apiVersion: v1alpha1
images:
- name: demo
  from: ubuntu:16.04
  package:
    repo: 
    - deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8
    - deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial edge
    gpg: 
    - https://bazel.build/bazel-release.pub.gpg
    - https://download.docker.com/linux/ubuntu/gpg
    install:
    - bazel
    - python-dev
    - ca-certificates
    - curl
    - build-essential
    - git
    - gcc
    - python-setuptools
    - lsb-release
    - software-properties-common
    - docker-ce=17.12.0~ce-0~ubuntu
  external:
  - src: https://storage.googleapis.com/kubernetes-release/release/v1.10.0/bin/linux/amd64/kubectl
    dst: /usr/local/bin/kubectl

  - src: https://github.com/kubernetes-sigs/kustomize/releases/download/v1.0.8/kustomize_1.0.8_linux_amd64
    dst: /usr/local/bin/kustomize
    sha256: b5066f7250beb023a3eb7511c5699be4dbff57637ac4a78ce63bde6e66c26ac4

  - src: https://storage.googleapis.com/kubernetes-helm/helm-v2.10.0-linux-amd64.tar.gz
    dst: /tmp/helm
    install:
    - install /tmp/helm/linux-amd64/helm /usr/local/bin/helm

  - src: https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-217.0.0-linux-x86_64.tar.gz
    dst: /tmp
source
Mopy (YAML)
GitHub – cmdjulian/mopy: mopy is a buildkit frontend to automatically build high efficient python based docker images in a custom dsl – no more docker specific skills required! 🐋
mopy is a buildkit frontend to automatically build high efficient python based docker images in a custom dsl – no more docker specific skills required! 🐋 – GitHub – cmdjulian/mopy: mopy is a buildkit frontend to automatically build high efficient python based docker images in a custom dsl – no more docker specific skills required! 🐋
github.com
Mopyfile.yaml
#syntax=cmdjulian/mopy:v1

apiVersion: v1
python: 3.9.2
build-deps: [ libopenblas-dev, gfortran, build-essential ]
labels:
  fizz: buzz
  foo: ${fizz}
envs:
  PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python
indices:
  - url: https://mirrors.sustech.edu.cn/pypi/simple
pip:
  - numpy==1.22
  - slycot
  - git+https://user:[email protected]/moskomule/anatome.git@dev
  - git+ssh://[email protected]/RRZE-HPC/pycachesim.git
  - ./requirements.txt
project: my-python-app/
source
Dockerfile (rendered)
FROM python:3.9.2 AS builder
RUN mkdir /build
WORKDIR /build

RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt \
    apt update && \
    apt install -y git-lfs libopenblas-dev gfortran build-essential
ENV PIP_NO_WARN_SCRIPT_LOCATION=0 \
    PIP_USER=1 \
    PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python \
    PYTHONPYCACHEPREFIX="$HOME/.pycache" \
    GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" \
    PIP_DISABLE_PIP_VERSION_CHECK=1
RUN --mount=type=cache,target=/root/.cache \
    --mount=type=ssh,required=true \
    --mount=type=bind,source=./requirements.txt,target=/tmp/0requirements.txt \
    pip install \
        --retries 2 \
        --extra-index-url https://mirrors.sustech.edu.cn/pypi/simple \
        -r /tmp/0requirements.txt \
        numpy==1.22 slycot git+https://user:[email protected]/moskomule/anatome.git@dev git+ssh://[email protected]/RRZE-HPC/pycachesim.git
RUN find /root/.local/lib/python*/ -name 'tests' -exec rm -r '{}' + && \
    find /root/.local/lib/python*/site-packages/ -name '*.so' -exec sh -c 'file "{}" | grep -q "not stripped" && strip -s "{}"' \; && \
    find /root/.local/lib/python*/ -type f -name '*.pyc' -delete && \
    find /root/.local/lib/python*/ -type d -name '__pycache__' -delete

FROM gcr.io/distroless/python3:nonroot@sha256:ddc151b6cb50be22362cfbe1c9e073570b06925162a908eb8321dede8c209997
LABEL org.opencontainers.image.description="autogenerated by mopy" \
      moby.buildkit.frontend="mopy" \
      mopy.version="v1" \
      mopy.python.version="3.9.2" \
      mopy.sbom="[\"numpy==1.22\", \"slycot\", \"git+https://github.com/moskomule/anatome.git@dev\", \"git+ssh://[email protected]/RRZE-HPC/pycachesim.git\", \"./requirements.txt\"]" \
      fizz="buzz" \
      foo="buzz"
ENV PYTHONUNBUFFERED=1 \
    PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
COPY --from=builder --chown=nonroot:nonroot /root/.local/ /home/nonroot/.local/
COPY --chown=nonroot:nonroot ./my-python-app/ /home/nonroot/my-python-app
ENTRYPOINT [ "python" ]
WORKDIR /home/nonroot/my-python-app
CMD [ "main.py" ]
source
Cargo Wharf (Rust)
GitHub – denzp/cargo-wharf: Cacheable and efficient Docker images builder for Rust
Cacheable and efficient Docker images builder for Rust – GitHub – denzp/cargo-wharf: Cacheable and efficient Docker images builder for Rust
github.com
Cargo.toml
# syntax = localhost:10395/denzp/cargo-wharf-frontend:local

[workspace]
members = [
    "binary-1",
    "binary-2",
    "lib-1",
]

[workspace.metadata.wharf.builder]
image = "rust"

[workspace.metadata.wharf.output]
image = "debian:stable-slim"
workdir = "/root"
user = "root"
pre-install-commands = [
  { shell = "echo 'pre-install' > /custom-setup", display = "My custom pre-install command" },
]
post-install-commands = [
  { shell = "echo 'post-install' > /custom-post-setup", display = "My custom post-install command" },
]
source
Nix
GitHub – reproducible-containers/buildkit-nix: Nix derivations as Dockerfiles (`docker build -f default.nix .`)
Nix derivations as Dockerfiles (`docker build -f default.nix .`) – GitHub – reproducible-containers/buildkit-nix: Nix derivations as Dockerfiles (`docker build -f default.nix .`)
github.com
flake.nix
# syntax = ghcr.io/reproducible-containers/buildkit-nix:v0.1.0@sha256:c727e0efc2a3aa23bbd31404701b5eee420ada1f08c7d4e21d666f24804355b6

{
  inputs.flake-utils.url = "github:numtide/flake-utils";
  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        # See https://ryantm.github.io/nixpkgs/languages-frameworks/rust/
        app = pkgs.rustPlatform.buildRustPackage {
          name = "rust-httpserver";
          cargoSha256 = "N8HCmBEiIX5G3F2OQH5IvkzpwhCJVpR51TB86gV9IAo=";
          src = ./.;
        };
      in rec {
        defaultPackage = pkgs.dockerTools.buildImage {
          name = "rust-httpserver";
          tag = "nix";
          contents = [ pkgs.bash pkgs.coreutils app ];
          config = {
            Cmd = [ "rust-httpserver" ];
            ExposedPorts = { "80/tcp" = { }; };
          };
        };
      });
}
source

escape

PowerShellのエスケープ文字は「`」(backtick) であって「\」(backslash) ではない。
特にWindowsではbackslashはドライブやディレクトリのデリミタなので、Dockerfileのデフォルト(backslash)のままだと RUN とかで複数行に書きたい時におかしくなってしまう。

Dockerfile
FROM microsoft/nanoserver

# ↓この行末の"\"がエスケープ文字とみなされてしまう
COPY testfile.txt c:\\
RUN dir c:\

# 「COPY testfile.txt c:\RUN dir c:」になってしまう

backtickをエスケープ文字として明示すれば回避できる。

Docker
# escape=`

FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\

トップレベルARG

(※ あくまで自分がそう呼んでるだけです)
Multi-stage buildsが導入され、 FROM が複数書けるようになったのはいいが、共通の ARG をどう指定するか。

実は最初の FROM の前にグローバルな ARG が置ける。
逆に FROM(stage内)にある ARG はそのstage内がスコープになる。

つまり、FROM にはトップレベル ARG がそのまま使えるが、stage内の RUN とかには別途受け皿となる ARG を置かないといけない

Docker
# トップレベルARG
ARG VERSION=1.34.1

# FROMにはそのまま使える
FROM busybox:$VERSION

# FROMの後(stage内)では受け皿となるARGが別途必要。名前は同じで良いっぽい
ARG VERSION
RUN echo $VERSION > image_version

Predefined ARGs

ARG は基本的に前もって宣言しておかないと使えないが、宣言しなくても使える ARG がいくつか用意されている。

  • HTTP_PROXY / http_proxy
  • HTTPS_PROXY / https_proxy
  • FTP_PROXY / ftp_proxy
  • NO_PROXY / no_proxy
  • ALL_PROXY / all_proxy

実は ARGdocker history で見えてしまうのもあって、秘匿しておきたい情報の受け渡しには向かない。
しかし、private repositoryのアクセス先などをbuild時に渡すこともある。

上記のPredefinedな ARG をそのまま使えば、 docker history には現れなくなる。
ただし ARG として明示してしまうとPredefinedの扱いではなくなり、 docker history で見えるようになるので注意が必要。


ヒアドキュメント

docker/dockerfile:1.4 で追加された。

RUNCOPY 限定でヒアドキュメントが使える。
しかもshebangも指定できる。ちょっとビックリ。

Dockerfile (with shebang)
# syntax=docker/dockerfile:1
FROM python:3.6
RUN <<EOT
#!/usr/bin/env python
print("hello world")
EOT

第1弾はひとまずここまで。
これら以外にも気になったところはあるので、それらはベストプラクティスも絡めて第2弾以降に。

  • RUN の引数
    • --mount
    • --network
  • COPY --link
  • Dockerfileをstdinで渡す
  • レイヤー生成の変更点
  • apt-get clean の自動実行

など…

コメントする