Docker

CircleCI Cloud Build JIB

インストール: Ubuntu18.04

# インストール
sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
sudo apt-get update -y -qq
# To list the versions, run `apt-cache madison docker-ce`
DOCKER_VERSION='5:19.03.1~3-0~ubuntu-bionic'
sudo apt-get install -y docker-ce=$DOCKER_VERSION docker-ce-cli=$DOCKER_VERSION containerd.io

# sudoなしで実行できるようにする
sudo gpasswd -a $USER docker
sudo chmod 666 /var/run/docker.sock

Ref: https://qiita.com/iganari/items/fe4889943f22fd63692a

docker-compose のインストール

VERSION="1.25.3"
sudo curl -L "https://github.com/docker/compose/releases/download/${VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 実行権限をつける
sudo chmod +x /usr/local/bin/docker-compose

# bash補完を入れる
sudo curl -L https://raw.githubusercontent.com/docker/compose/${VERSION}/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose

Ref:
Docker Compose のインストール| Docker ドキュメント

CLI

docker run

ボリュームマウント

--mountまたは-vオプションを使う。 --mountの方が推奨されている。

例:

docker run \
  --mount type=bind,source=/tmp,target=/usr

docker run \
--mount type=bind,source=/tmp,target=/usr,readonly

Ref: https://docs.docker.com/storage/volumes/

実行が終了したら破棄

docker run --rm [image] [COMMAND]

コンテナ全削除

docker stop `docker ps -a -q`; docker rm `docker ps -a -q`
docker stop $(docker ps -q); docker rm $(docker ps -q -a); docker rmi $(docker images -q) --force

buildrunをワンライナーで

docker build -t [イメージ名] . && docker run -itd --rm --name [イメージ名] [イメージ名]

プライベートレジストリ

v2 API を使う

まず、認証用の URL を取得する。

# `-D -`でレスポンスヘッダを表示
curl -D - https://my-registry.com/v2/

# 以下のように Www-Authenticateヘッダに認証用のURLをもらえる
# Www-Authenticate: Bearer realm="https://my-registry.com:5003/auth",service="Docker registry"

次に、認証トークンを取得する。

curl -u <user>:<password> -d service="Docker registry" -d scope="<scope>" -d account=<account> https://my-registry.com:5003/auth
# { "token": ******** }

取得した認証トークンを使って v2API を利用する。

curl

# 指定したイメージのタグ一覧を取得 (リクエストにベアラトークンが必要)
curl https://my-registry.com/v2/hoge/my-app/tags/list

::: tips トークンを取得するときのscopeが正しくないと v2API の応答が UNAUTHORIZED になる。 :::

プライベートリポジトリから pull

docker login artifactory.***.com

# ログインできない場合はデバッグ
docker -D -l login artifactory.***.com

# httpsは省略できる
docker pull artifactory.***.com/hoge-snapshot/myApp:latest

docker images
docker run artifactory.***.com/hoge-snapshot/myApp:latest

ホスト PC のファイルをコンテナへコピー

Win 環境だとコンテナパスのルートは//で書かないといけない。

docker cp ./hoge.txt myContainer://test/

Dockerfile

FROM openjdk:8-jdk-alpine

# bashがないイメージでbashを使いたい場合は入れる
RUN apk add --no-cache bash

# ディレクトリをコピーする場合はコピー先でディレクトリ名まで指定する必要があるので注意
ADD src /project/src
ADD .gradle /project/.gradle
ADD gradle /project/gradle
ADD build.gradle /project
ADD gradlew /project
ADD gradlew.bat /project
ADD settings.gradle /project

WORKDIR /project

#ENTRYPOINT ["ehco", "hello"]
ENTRYPOINT ["./gradlew", "bootrun"]

プライベートレジストリへ push

docker -t hoge .
docker tag hoge myrepo.aaa.com/aaa/bbb/ccc/hoge:latest
docker push myrepo.aaa.com/aaa/bbb/ccc/hoge:latest

Docker Compose

ex)

version: "2"
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - 3306:3306
  rabbitmq:
    image: rabbitmq:3-management
    ports:
      - 5672:5672
      - 15672:15672
  app:
    image: hoge/myapp:latest
    depends_on:
      - db
      - rabbitmq

アプリのコンテナから mysql コンテナへ接続する

sudo apt update
sudo apt install mysql-client
mysql -u root -ppassword -h db
docker-compose up
# imageのビルドも行う場合は--buildをつけるとWarningが消える
docker-compose up --build

ディレクトリ指定でdocker-compose

ref: stackoverflow | “docker-compose” command to set path for docker-compose.yml file

# ./hoge/docker-compose.ymlを読み込む
docker-compose -f `pwd`/hoge/docker-compose.yml up

-fオプションで指定するパスは絶対パスである必要がある。

docker runで環境変数を設定

docker run -it -e

サーバー起動後に初期化処理

データベース立ち上げ後に初期データを挿入するスクリプトを動かしたいときなど。

サーバーのポートが繋がるまで待機してから何かしらするスクリプトを書く。 wait_and_doSomething.sh

until curl localhost:8091 ; do
   sleep 3
done
# 初期データ挿入など...

書いたスクリプトをバックグラウンドで動かしつつエントリポイントを叩くスクリプトを書く。 startup.sh

./wait_and_doSomething.sh &
./entrypoint.sh

entrypoint.shの呼び方はコンテナイメージによって違う。

Dockerfile でENTRYPOINTを上書きする。

FROM hogehoge

ADD startup.sh .
ADD wait_and_doSomething.sh .

ENTRYPOINT ["./startup.sh"]

ホストとネットワークを共有する

localhostで立ち上げた DB をコンテナから見たいときとか。

https://qiita.com/harasou/items/1f86d8db86bf88e794bc

--net=hostオプションをつかう。

docker run -itd --rm --name app --net=host my-app:latest

コンテナのログ監視

docker logs [コンテナ名]
# `-f`オプションで`tail`的なことができる
docker logs -f [コンテナ名]

使ってないリソースを削除

docker rmi $(docker images -q)
docker system prune
docker volume prune

Tips

docker runで複数行の bash コマンドを実行する

変数は\$とエスケープしないと外側のスクリプトの変数を参照してしまうので注意。

docker run -i --rm \
  asciidoctor/docker-asciidoctor:1.0.0 \
  sh << EOT
  #!/usr/bin/env bash
  test="hoge"
  echo \$test
  asciidoctor -r asciidoctor-diagram -D /dist index.adoc
EOT

Ubuntu:18.04

FROM ubuntu:18.04

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion

Ref: http://docs.docker.jp/engine/articles/dockerfile_best-practice.html#id6

コンテナ自動再起動

Docker 起動時に自動的に起動したいコンテナなどに

docker run --restart=always

Python3.7 の Dockerfile

FROM python:3.7.3

WORKDIR /root
COPY . .

ENTRYPOINT ["python3", "..."]

Gradle + Kotlin の Dockerfile

FROM openjdk:8-jdk-alpine as base
WORKDIR /root
COPY gradle/ gradle/
COPY build.gradle .
COPY gradlew .
COPY settings.gradle .
COPY src/ src/
RUN ./gradlew || true

FROM openjdk:8-jdk-alpine as runner
WORKDIR /root

COPY gradle/ gradle/
COPY build.gradle .
COPY gradlew .
COPY settings.gradle .
COPY src/ src/
COPY --from=base /root/.gradle/ /root/.gradle/

RUN ./gradlew run

Dockerfile でバージョン文字列などを変数にする

ARGを使う。RUN exportだと動かない。

ARG VERSION_GRADLE='5.6.2'

RUN apt -qq install -y wget unzip
RUN wget "https://services.gradle.org/distributions/gradle-${VERSION_GRADLE}-bin.zip" -P /tmp
RUN unzip -d /opt/gradle /tmp/gradle-*.zip && rm /tmp/gradle-*.zip

Ref: https://blog.lorentzca.me/use-argument-in-dockerfile/

docker build時に--build-argで値を指定することもできる。

docker build . --build-arg VERSION_GRADLE=5.6.2

任意の Docker イメージでデバッグする

docker run --rm -itd --name debug <image>
docker exec -it debug sh

# デバッグ終わったらコンテナを止めて終了
docker stop debug

alpine に yq を入れる

yqは yaml パーサ。使い方はjqと同じ。

FROM alpine:3.8

RUN apk add --update py-pip jq \
    && pip install --upgrade pip \
    && pip install yq

docker run で引数付きコマンドで entrypoint を上書きする

--entrypointではコマンドのみ指定し、コマンドの一番最後に引数とオプションを書く。

docker run --entrypoint ls my-image . -la

Ref: https://stackoverflow.com/questions/41694329/docker-run-override-entrypoint-with-shell-script-which-accepts-arguments

コンテナのシステム時刻を上書きする

WORKDIR /
RUN sudo git clone https://github.com/wolfcw/libfaketime.git
WORKDIR /libfaketime/src
RUN sudo make install
RUN echo '/usr/local/lib/faketime/libfaketime.so.1' | sudo tee -a /etc/ld.so.preload
ENV DONT_FAKE_MONOTONIC=1
ENV FAKETIME_CACHE_DURATION=1
ENV FAKETIME='@2000-10-10 09:00:00'

Ref: https://qiita.com/Targityen/items/67682d6c80cdcbe1186c

ディレクトリ内を再帰的に COPY する

こんなディレクトリ構成のときに、root ディレクトリごと COPY したいとき。

root/
  - sub/
    - file01
    - file02
  - file01

*を使って書けば OK。

COPY root/* ./

Dockerfile の COPY で親ディレクトリのファイルを参照する

このようなフォルダ階層のときに、hoge.txtを COPY したい。

project
  docker
    my-image
      Dockerfile
  hoge.txt

Dockerfile では、projectをカレントディレクトリとしてパスを書く。

FROM alpine

# ../../hoge.txt ではない
COPY hoge.txt .

docker build-fオプションに Dockerfile のパスを指定すると、コマンド実行時の場所をカレントとしてイメージのビルドが行われる。

cd project
docker build -t my-image -f docker/my-image/Dockerfile .

Linux Distribution を調べる

cat /etc/*-release

COPY と一緒に chown

USERを指定した場合はCOPYしたファイルはchownしないとファイルの権限でエラーになるが、--chownオプションを使ってCOPYすれば権限も一緒に指定できる。

USER hoge
COPY --chown hoge:hoge . /app

# こうすると以下のコマンドを実行する必要がなくなる
RUN chown -R hoge:hoge /app

ADD か COPY か

とりあえずCOPYを使えばいい。

Ref: https://nickjanetakis.com/blog/docker-tip-2-the-difference-between-copy-and-add-in-a-dockerile

イメージ起動時になにもしない ENTRYPOINT

何もしないけど終了もしない。 コンテナのデバッグをしたいときに。

ENTRYPOINT ["tail", "-f", "/dev/null"]

マルチステージビルドで node_module をキャッシュする

Nextjs アプリの Dockerfile の例:

# package.jsonが変わった時だけ依存関係をダウンロードするようにする
FROM node:10-alpine as node_cache

WORKDIR /next_cache/
RUN yarn add next@8.1.0

WORKDIR /node_cache/
ADD package.json ./
ADD yarn.lock ./
# --no-progressをつけたほうが早い
RUN yarn install --no-progress

# ビルド専用コンテナ。成果物を/distに生成する
FROM node:10-alpine as builder

WORKDIR /dist/
COPY --from=node_cache /node_cache/node_modules/ ./node_modules/
ADD . ./
RUN yarn build

# 実行用コンテナ
FROM node:10-alpine as runner

WORKDIR /app/
# nextだけ入れたnode_modulesをコピー
COPY --from=node_cache /node_cache/node_modules/ ./node_modules/
# 成果物(.next)をコピー
COPY --from=builder /dist/.next/ ./.next/
ADD ./package.json .

ENTRYPOINT ["yarn", "server"]

Ref: https://github.com/jstandish/cached-node-module-build-example/blob/master/Dockerfile

マルチステージビルドで重複する設定を共通化する

最初にベースのコンテナを作り、以降はFROMでベースコンテナを指定する。 すると各コンテナはベースコンテナの設定を引き継ぐことができる。 環境変数を共通で設定したい場合などに便利。

FROM alpine as base

USER my-user
ENV HOGE=123

FROM base as builder
# ...

FROM base as runner
# ...

Ref: https://github.com/moby/moby/issues/37345

指定した文字列を含むイメージを全削除

# 'gcr'を含むイメージを全削除
docker rmi $(docker images | grep gcr | tr -s ' ' | cut -d ' ' -f 3)

Docker コンテナが起動中か確認する

docker inspect -f {{.State.Running}} <container name>

Ref: https://stackoverflow.com/a/33520390

コンテナ内の日付を変えたい

システム日付を変更したコンテナ環境でテストしたい

JIB でデプロイ

Docker デーモンなしでイメージを push できるらしい。

plugins {
    id 'com.google.cloud.tools.jib' version '1.0.2'
}

repositories {
    mavenCentral()
    maven { url = 'https://artifactory.***/jcenter' }
}

artifactory {
    contextUrl = "https://artifactory.***"
    publish {
        repository {
            repoKey = 'my-docker-snapshot-local'

            // set Circle CI Environment Variables:
            // - ORG_GRADLE_PROJECT_ARTIFACTORY_USER
            // - ORG_GRADLE_PROJECT_ARTIFACTORY_PASSWORD
            username = project.hasProperty("ARTIFACTORY_USER") ? ARTIFACTORY_USER : ""
            password = project.hasProperty("ARTIFACTORY_PASSWORD") ? ARTIFACTORY_PASSWORD : ""
        }
        defaults {
            publications('mavenJava')
            publishArtifacts = true
        }
    }
}

jib {
    from {
        image = 'openjdk:8u171-jdk-alpine'
    }

    to {
        def version = "$System.env.CIRCLE_BUILD_NUM"
        def target  = ("$System.env.CIRCLE_BRANCH" == "master") ? "release" : "snapshot"
        image = "artifactory.***/my-docker-${target}-local/hoge-api:${version}"

        auth {
            username = "$System.env.ARTIFACTORY_USER"
            password = "$System.env.ARTIFACTORY_KEY"
        }
    }

    container {
        mainClass = 'com.hoge.fuga.ApplicationKt'

        // workingDirectoryを指定してそこにconfig/application.ymlを後から配置すればapplication.ymlを読み込んでくれる
        appRoot = "/opt/app"
        workingDirectory = "/opt/app"

        jvmFlags = ['-Xdebug']
        ports = ['8080']
    }
}

docker-ls

brew install docker-ls

使い方。

# 指定したリポジトリタグ一覧を取得
docker-ls tags -r <registory url> -u <user name> -p <password> <repository>
# ex) docker-ls tags -r https://my-registry.com -u hoge -p pass hoge/my-app

FAQ

docker runがすぐ終了する

  • コンテナに持っていくファイルがLFか確認する standard_init_linux.go:190: exec user process caused "no such file or directory"

Apline

ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.8/main: operation timed out

apk updateする。