Skip to content
Latchkey LogoLatchkey home

Build and Push Docker Images workflow (huggingface/OpenEnv)

The Build and Push Docker Images workflow from huggingface/OpenEnv, explained and optimized by Latchkey.

Source: huggingface/OpenEnv.github/workflows/docker-build.ymlLicense BSD-3-ClauseView source
C
CI health grade
56/100
Health score
2
Jobs per trigger
3
Fixes in the optimized version

In short

  1. CI health: C (fair)
  2. The optimized version below points runs-on at Latchkey managed runners and adds run de-duplication and job timeouts.

What it does

This is the Build and Push Docker Images workflow from the huggingface/OpenEnv repository, a real project running GitHub Actions. It is shown here with attribution under its BSD-3-Clause license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Build and Push Docker Images

on:
  push:
    branches:
      - main
    paths:
      - 'envs/**/Dockerfile'
      - 'envs/**/*.py'
      - '.github/workflows/docker-build.yml'
  workflow_dispatch:  # Allow manual trigger

env:
  REGISTRY: ghcr.io
  IMAGE_PREFIX: ${{ github.repository_owner }}/openenv

jobs:
  # Job 1: Build base image first
  build-base:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v7

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Log in to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata for base image
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-base
          tags: |
            type=raw,value=latest,enable={{is_default_branch}}
            type=sha

      - name: Build and push base image
        uses: docker/build-push-action@v7
        with:
          context: .
          file: src/openenv/core/containers/images/Dockerfile
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha,scope=base
          cache-to: type=gha,mode=max,scope=base
  # Job 2: Build environment images (depends on base)
  build-envs:
    runs-on: ubuntu-latest
    needs: build-base  # Wait for base image to be built
    permissions:
      contents: read
      packages: write

    strategy:
      fail-fast: false
      matrix:
        image:
          - name: echo-env
            dockerfile: envs/echo_env/server/Dockerfile
            context: envs/echo_env
          - name: chat-env
            dockerfile: envs/chat_env/server/Dockerfile
            context: envs/chat_env
          - name: coding-env
            dockerfile: envs/coding_env/server/Dockerfile
          - name: sumo-rl-env
            dockerfile: envs/sumo_rl_env/server/Dockerfile
            context: envs/sumo_rl_env
          - name: atari-env
            dockerfile: envs/atari_env/server/Dockerfile
            context: envs/atari_env
          - name: git-env
            dockerfile: envs/git_env/server/Dockerfile
            context: envs/git_env
          - name: connect4_env
            dockerfile: envs/connect4_env/server/Dockerfile
            context: envs/connect4_env
          - name: chess-env
            dockerfile: envs/chess_env/server/Dockerfile
            context: envs/chess_env
          - name: tbench2-env
            dockerfile: envs/tbench2_env/server/Dockerfile
            context: envs/tbench2_env
          - name: textarena-env
            dockerfile: envs/textarena_env/server/Dockerfile
            context: envs/textarena_env
          - name: browsergym-env
            dockerfile: envs/browsergym_env/server/Dockerfile
            context: envs/browsergym_env
          - name: snake-env
            dockerfile: envs/snake_env/server/Dockerfile
            context: envs/snake_env
          - name: unity-env
            dockerfile: envs/unity_env/server/Dockerfile
            context: envs/unity_env
          - name: opencode-env
            dockerfile: envs/opencode_env/server/Dockerfile
            context: envs/opencode_env
          - name: openapp-env
            dockerfile: envs/openapp_env/server/Dockerfile
            context: envs/openapp_env
          - name: maze-env
            dockerfile: envs/maze_env/server/Dockerfile
            context: envs/maze_env
          - name: openspiel-env
            dockerfile: envs/openspiel_env/server/Dockerfile
            context: envs/openspiel_env

    steps:
      - name: Checkout code
        uses: actions/checkout@v7

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Log in to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.image.name }}
          tags: |
            type=raw,value=latest,enable={{is_default_branch}}
            type=sha

      - name: Build and push environment image
        uses: docker/build-push-action@v7
        with:
          context: ${{ matrix.image.context || '.' }}
          file: ${{ matrix.image.dockerfile }}
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha,scope=${{ matrix.image.name }}
          cache-to: type=gha,mode=max,scope=${{ matrix.image.name }}
          build-args: |
            BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-base:latest

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

workflow (.yml), optimized
name: Build and Push Docker Images

on:
  push:
    branches:
      - main
    paths:
      - 'envs/**/Dockerfile'
      - 'envs/**/*.py'
      - '.github/workflows/docker-build.yml'
  workflow_dispatch:  # Allow manual trigger

env:
  REGISTRY: ghcr.io
  IMAGE_PREFIX: ${{ github.repository_owner }}/openenv

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # Job 1: Build base image first
  build-base:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    runs-on: latchkey-small
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v7

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Log in to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata for base image
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-base
          tags: |
            type=raw,value=latest,enable={{is_default_branch}}
            type=sha

      - name: Build and push base image
        uses: docker/build-push-action@v7
        with:
          context: .
          file: src/openenv/core/containers/images/Dockerfile
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha,scope=base
          cache-to: type=gha,mode=max,scope=base
  # Job 2: Build environment images (depends on base)
  build-envs:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    runs-on: latchkey-small
    needs: build-base  # Wait for base image to be built
    permissions:
      contents: read
      packages: write

    strategy:
      fail-fast: false
      matrix:
        image:
          - name: echo-env
            dockerfile: envs/echo_env/server/Dockerfile
            context: envs/echo_env
          - name: chat-env
            dockerfile: envs/chat_env/server/Dockerfile
            context: envs/chat_env
          - name: coding-env
            dockerfile: envs/coding_env/server/Dockerfile
          - name: sumo-rl-env
            dockerfile: envs/sumo_rl_env/server/Dockerfile
            context: envs/sumo_rl_env
          - name: atari-env
            dockerfile: envs/atari_env/server/Dockerfile
            context: envs/atari_env
          - name: git-env
            dockerfile: envs/git_env/server/Dockerfile
            context: envs/git_env
          - name: connect4_env
            dockerfile: envs/connect4_env/server/Dockerfile
            context: envs/connect4_env
          - name: chess-env
            dockerfile: envs/chess_env/server/Dockerfile
            context: envs/chess_env
          - name: tbench2-env
            dockerfile: envs/tbench2_env/server/Dockerfile
            context: envs/tbench2_env
          - name: textarena-env
            dockerfile: envs/textarena_env/server/Dockerfile
            context: envs/textarena_env
          - name: browsergym-env
            dockerfile: envs/browsergym_env/server/Dockerfile
            context: envs/browsergym_env
          - name: snake-env
            dockerfile: envs/snake_env/server/Dockerfile
            context: envs/snake_env
          - name: unity-env
            dockerfile: envs/unity_env/server/Dockerfile
            context: envs/unity_env
          - name: opencode-env
            dockerfile: envs/opencode_env/server/Dockerfile
            context: envs/opencode_env
          - name: openapp-env
            dockerfile: envs/openapp_env/server/Dockerfile
            context: envs/openapp_env
          - name: maze-env
            dockerfile: envs/maze_env/server/Dockerfile
            context: envs/maze_env
          - name: openspiel-env
            dockerfile: envs/openspiel_env/server/Dockerfile
            context: envs/openspiel_env

    steps:
      - name: Checkout code
        uses: actions/checkout@v7

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Log in to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.image.name }}
          tags: |
            type=raw,value=latest,enable={{is_default_branch}}
            type=sha

      - name: Build and push environment image
        uses: docker/build-push-action@v7
        with:
          context: ${{ matrix.image.context || '.' }}
          file: ${{ matrix.image.dockerfile }}
          push: true
          platforms: linux/amd64,linux/arm64
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha,scope=${{ matrix.image.name }}
          cache-to: type=gha,mode=max,scope=${{ matrix.image.name }}
          build-args: |
            BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-base:latest

What changed

  1. Run on Latchkey managed runners with one line (runs-on), which apply the fixes below automatically and self-heal transient failures. Each Linux x64 job moves to the Latchkey size matching its vCPU count, latchkey-small for a standard 2 vCPU runner; macOS, Windows and arm64 jobs stay as written.
  2. Cancel superseded runs when a branch or PR gets a newer push.
  3. Add a job timeout so a hung step cannot burn hours of runner time.

4 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

  • Container pulls and builds

This workflow runs 2 jobs (18 with the matrix expanded) per trigger. All of them run on a standard GitHub-hosted Linux x64 runner, which in a private repository bills $0.006 a minute against $0.0025 on latchkey-small, 58% lower; in a public repository like this one they are free. Estimate the monthly difference with your own run counts.

Actions used in this workflow

  • actions/checkout
  • docker/setup-buildx-action
  • docker/login-action
  • docker/metadata-action
  • docker/build-push-action

Frequently asked questions

What does the Build and Push Docker Images workflow (huggingface/OpenEnv) workflow do?
This is the Build and Push Docker Images workflow from the huggingface/OpenEnv repository, a real project running GitHub Actions. It is shown here with attribution under its BSD-3-Clause license.
What CI health grade does this workflow get?
This Docker workflow grades C. Paste your own workflow into the Latchkey grader to see its grade and the exact fixes.
How can I improve this Docker workflow?
Add run de-duplication and job timeouts. Latchkey applies these automatically on managed runners when you point runs-on at Latchkey.

References