GitLab Runner: Dockerイメージのビルド

StackheroランナーとDocker-in-Dockerを活用し、GitLab CI/CDパイプラインから効率的にDockerイメージをビルド&プッシュする方法

👋 Stackhero ドキュメントへようこそ!

Stackhero では、GitLab Runner cloud のシンプルなソリューションを提供しており、GitLab CI/CD ジョブの実行を効率的かつ手間なく行えます。主な特長は以下の通りです:

  • 無制限の CI/CD 分数:パイプラインを必要なだけ何度でも実行でき、分単位の課金や予期しない追加料金はありません。
  • 複数のジョブを同時実行:複数のジョブを並列で実行し、開発スピードを向上できます。
  • Docker executorDocker-in-Docker サポート:CI/CD プロセスの一部として、コンテナイメージのビルドやプッシュを簡単に行えます。
  • GitLab.com および セルフマネージド GitLab インスタンスの両方にシームレスに対応。
  • 高速な NVMe/SSD ストレージを備えたプライベートかつ専用のインフラストラクチャにより、安定した予測可能なビルドパフォーマンスを実現します。
  • 🇪🇺 ヨーロッパ および 🇺🇸 USA のリージョンでご利用いただけますので、チームのニーズに合わせて選択可能です。

時間を節約:最初の GitLab Runner を接続し、数分でパイプラインの実行を開始できます!

StackheroのGitLab Runnerでは、すべてのジョブがDocker executorを使って新しいコンテナ内で実行されます。Docker-in-Docker(DinD)を有効にすることで、パイプライン内で直接Dockerイメージをビルドできます。この構成では、ジョブと並行してDockerデーモンが起動し、docker builddocker pushコマンドをCI/CDプロセスの一部として実行できるようになります。

すべての実行は無制限のCI/CD分の恩恵を受けます。ビルド回数に制限はなく、必要なだけ何度でもビルド可能です。ビルドキャッシュはランナー専用ディスクに保存されるため、繰り返しのビルドでは以前のレイヤーを再利用できます。これによりビルド時間が大幅に短縮され、パイプラインの完了も高速化されます。

以下のサンプル.gitlab-ci.ymlをリポジトリに追加できます。この設定は、プロジェクトルートのDockerfileをビルドします。

build-image:
  stage: build
  image: docker:29
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - docker info
  script:
    # "my-image"を任意のイメージ名に置き換えてください:
    - docker build -t my-image .
    # 必要に応じて、ビルドしたイメージで簡単なテストを実行できます:
    # - docker run --rm my-image /path/to/tests

この例ではDockerイメージのバージョン29を使用しています。新しいバージョンが利用可能な場合は、そちらを使用することもできます。最新のタグは公式Dockerイメージページで確認できます。

この構成では、docker:29-dindサービスがジョブと並行してDockerデーモンを起動し、DOCKER_HOST: "tcp://docker:2375"docker CLIがそのデーモンを利用するよう指定しています。docker:dindサービスを宣言する場合は必ずDOCKER_HOSTを設定してください。 これを設定しないと、CLIが別のデーモンにサイレントに接続してしまい、サービスの設定ミスが隠れてしまいます(Testcontainersのようなサービス依存ツールも正常に動作しません)。DOCKER_TLS_CERTDIR: ""は、ジョブ内部ネットワーク上の非TLSポート2375で接続する設定です。

よりシンプルな方法として、servicesブロックや変数を省略することも可能です。Stackheroのランナーは、マウントされたソケット経由で利用可能なDockerデーモンも提供しているため、追加設定なしでdocker buildがそのまま動作します。

Testcontainersは、Java、Go、Node.js、Python、.NETなど複数言語で利用できるテスト用ライブラリです。テスト実行中に実際のサービスを一時的なDockerコンテナとして起動します。データベースやメッセージブローカーをモックする代わりに、統合テストが本物のPostgreSQL、MySQL、Redis、Kafkaインスタンスと通信できるようになり、テスト前に作成・終了後に削除されます。特にJavaやSpringプロジェクトで広く利用されています。

TestcontainersはDockerデーモンを必要とするため、上記と同じDocker-in-Docker構成で動作します。追加の変数は不要です。

test:
  stage: test
  # テストに必要なイメージ(ここではJDK)を指定してください。必ずしもdockerイメージである必要はありません。
  image: gradle:jdk21
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  script:
    - gradle test

TestcontainersはDOCKER_HOSTを参照してデーモンを見つけ、起動したコンテナの公開ポートにも同じdockerホスト名でアクセスします。どちらもジョブのネットワーク上で解決されるため、問題なく動作します。

Testcontainersを使うジョブではDOCKER_HOSTを必ず設定してください。設定しない場合、TestcontainersはランナーがマウントしたDockerソケットを使おうとし、その後テストコンテナにホストIPアドレスで接続しようとしますが、ジョブからは接続できません。よくある症状は、RyukヘルパーコンテナがWait strategy failed. Container is removedTimed out waiting for log output matching '.*Started.*'で失敗することです。

GitLabは、パイプラインからプロジェクトのコンテナレジストリへ安全に認証・プッシュできるよう、事前定義変数(CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD, CI_REGISTRY_IMAGE)を提供しています。追加のシークレットは不要です。

以下はイメージをビルドし、プッシュするジョブの例です。

build-and-push:
  stage: build
  image: docker:29
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
    - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
    # デフォルトブランチの場合は"latest"タグでもプッシュできます:
    - |
      if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
        docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE:latest"
        docker push "$CI_REGISTRY_IMAGE:latest"
      fi

他のレジストリ(Docker Hubやプライベートレジストリなど)へプッシュする場合は、認証情報をCI/CD変数として保存し、同様にdocker loginで利用できます。

ランナーのディスクはパイプライン間で永続化されるため、イメージレイヤーをビルドキャッシュとして再利用できます。これにより繰り返しのビルドが大幅に高速化されます。以下はそのための設定例です。

build-cached:
  stage: build
  image: docker:29
  services:
    - name: docker:29-dind
      alias: docker
  variables:
    DOCKER_HOST: "tcp://docker:2375"
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    # キャッシュ用に最新イメージをプル(存在しない場合はスキップ):
    - docker pull "$CI_REGISTRY_IMAGE:latest" || true
    - docker build --cache-from "$CI_REGISTRY_IMAGE:latest" -t "$CI_REGISTRY_IMAGE:latest" .
    - docker push "$CI_REGISTRY_IMAGE:latest"

この方法により、Dockerのレイヤーキャッシュを活用し、新規または変更されたレイヤーのみ再ビルドされます。

同時実行できるジョブ数はご契約プランによって決まります。同じステージ内のジョブは、設定した並列数まで同時に開始されます。これにより、複数の独立したジョブが並列で実行され、最も遅いジョブが完了した時点でそのステージが終了します。逐次実行よりも効率的です。

例:

stages:
  - test

unit:
  stage: test
  image: node:22
  script: npm run test:unit

integration:
  stage: test
  image: node:22
  script: npm run test:integration

e2e:
  stage: test
  image: node:22
  script: npm run test:e2e

並列数を1以上に設定すれば、unitintegratione2eの各ジョブが同時に実行されます。

GitLab CI/CDパイプラインでのDockerイメージビルドの詳細については、公式GitLabドキュメント(Dockerビルドの利用)をご参照ください。