Ivan Vnucec
Ivan Vnucec

Reputation: 739

How to run cached Docker image in Github Action?

I don't know how to run a cached Docker image in Github Actions.
I've followed a tutorial about Publishing Docker images to implement a task that would cache, build and push Docker image to a DockerHub.
I need to build, cache and run the image, the image publishing is optional.
My goal is to speed up CI workflow.
Here is the Github Actions workflow:

name: CI

# Controls when the action will run. 
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - name: Check Out Repo
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

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

      - name: Cache Docker layers
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with: 
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v2
        with:
          context: ./
          file: ./Dockerfile
          builder: ${{ steps.buildx.outputs.name }}
          push: true
          tags: ivan123123/c_matrix_library:latest
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache

      #- name: Run Docker container
      #  run: ???

      # Upload gcovr code coverage report
      - name: Upload GCC Code Coverage Report
        uses: actions/upload-artifact@v2
        with:
          name: coveragereport
          path: ./builddir/meson-logs/coveragereport/
        
      - name: Upload code coverage reports to codecov.io page
        run: bash <(curl -s https://codecov.io/bash) 

Edit:
I've found no solution to running cached Docker image, but I have managed to build cached image every time I run CI workflow with docker/setup-buildx-action@v1 action. Because the image is cached, we don't need to download every Docker image dependencies thus saving time from 3 minutes originally to only 40 seconds. Below is the Github Actions workflow:

name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Check Out Repo
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

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

      - name: Cache register
        uses: actions/cache@v2
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ hashFiles('**/Dockerfile') }}

      - name: Build Docker image
        uses: docker/build-push-action@v2
        with:
          context: ./
          file: ./Dockerfile
          builder: ${{ steps.buildx.outputs.name }}
          load: true
          tags: c_matrix_library:latest
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache

      - name: Run Docker container
        run: docker run -v "$(pwd):/app" c_matrix_library:latest

Upvotes: 22

Views: 30818

Answers (5)

Lance Pollard
Lance Pollard

Reputation: 79248

For windows, it doesn't appear that buildx or docker/build-push-action work (for windows server github action environments). So something like this seems like it might work on windows:

on: workflow_dispatch
jobs:
  publish:
    # Windows Server 2022 (https://github.com/actions/runner-images)
    runs-on: windows-2022
    environment: build
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Create cache directory
        run: mkdir docker-cache
      - name: Cache docker image
        id: cache-docker
        uses: actions/cache@v4
        with:
          path: C:\Users\runneradmin\AppData\Local\docker-cache
          key: ${{ runner.os }}-docker-cache
      - name: Login docker
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
          GH_USERNAME: ${{ vars.GH_USERNAME }}
        run: echo $env:GH_TOKEN | docker login ghcr.io -u $env:GH_USERNAME --password-stdin
      - name: Pull docker image
        if: steps.cache-docker.outputs.cache-hit != 'true'
        run: docker pull swift:5.9.2-windowsservercore-ltsc2022 || true
      - name: Build docker
        if: steps.cache-docker.outputs.cache-hit != 'true'
        working-directory: load/docker
        run: docker build --cache-from swift:5.9.2-windowsservercore-ltsc2022 . -t lancejpollard/swift
      - name: Tag docker
        if: steps.cache-docker.outputs.cache-hit != 'true'
        working-directory: load/docker
        run: docker tag lancejpollard/swift:latest ghcr.io/lancejpollard/swift:latest
      - name: Save docker
        if: steps.cache-docker.outputs.cache-hit != 'true'
        run: docker save lancejpollard/swift -o "$($env:LOCALAPPDATA)/docker-cache/lancejpollard.swift.tar"
      - name: Restore docker
        if: steps.cache-docker.outputs.cache-hit == 'true'
        run: docker load < "$($env:LOCALAPPDATA)/docker-cache/lancejpollard.swift.tar"
      - name: Run docker
        working-directory: load/docker
        run: docker run -v ${PWD}:/ --name swift -i -d -t -p lancejpollard/swift

However, for this large swift image, which takes roughly 20 minutes to build, the cache still takes > 10 minutes to docker load. So not that much of an improvement.

Edit: Actually, testing a few more times, it takes ~15min to build my swift image without caching. It takes ~25min to docker save the .tar, so that is ~40min on the save path. It takes ~3min to load the cache, and ~10min to docker load, which is 13min also. So it's the same to build with the cache and without the cache. And if you need to save the cache, double or triple the build time. So it's a net loss to cache like this :/

Upvotes: 1

Lucas Bustamante
Lucas Bustamante

Reputation: 17188

If you want to cache a published Docker image that lives in the Docker Repository, you can do:

- name: Restore MySQL Image Cache if it exists
  id: cache-docker-mysql
  uses: actions/cache@v3
  with:
    path: ci/cache/docker/mysql
    key: cache-docker-mysql-5.7

- name: Update MySQL Image Cache if cache miss
  if: steps.cache-docker-mysql.outputs.cache-hit != 'true'
  run: docker pull mysql:5.7 && mkdir -p ci/cache/docker/mysql && docker image save mysql:5.7 --output ./ci/cache/docker/mysql/mysql-5.7.tar

- name: Use MySQL Image Cache if cache hit
  if: steps.cache-docker-mysql.outputs.cache-hit == 'true'
  run: docker image load --input ./ci/cache/docker/mysql/mysql-5.7.tar

- name: Start containers
  run: docker compose up -d

When docker compose up runs, if a service uses the Docker image mysql:5.7 image, it's going to skip downloading it.

Upvotes: 15

blthayer
blthayer

Reputation: 922

This question is a bit old now, but I've found the documented way of running a built image from the docker/build-push-action in a subsequent step. In short, you have to set up a local registry.

The yaml below has been directly copy + pasted from here.

name: ci

on:
  push:
    branches:
      - 'main'

jobs:
  docker:
    runs-on: ubuntu-latest
    services:
      registry:
        image: registry:2
        ports:
          - 5000:5000
    steps:
      -
        name: Checkout
        uses: actions/checkout@v3
      -
        name: Set up QEMU
        uses: docker/setup-qemu-action@v2
      -
        name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
        with:
          driver-opts: network=host
      -
        name: Build and push to local registry
        uses: docker/build-push-action@v3
        with:
          context: .
          push: true
          tags: localhost:5000/name/app:latest
      -
        name: Inspect
        run: |
          docker buildx imagetools inspect localhost:5000/name/app:latest

Upvotes: 4

m33n
m33n

Reputation: 1751

This might not fully answer you question since I think there is no actual way of running your cached image.

But you can speed up your build using Github's cache, I have posted a complete tutorial about this that you can read here

Summarizing you can setup Docker buildx and then use GH cache with build-push-action:

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

  - name: Build and push
    uses: docker/build-push-action@v2
    with:
      context: .
      file: ./Dockerfile
      push: true
      tags: ivan123123/c_matrix_library:latest
      cache-from: type=gha
      cache-to: type=gha

Edit

Just found a reference in build-push action that might be useful to you:

https://github.com/docker/build-push-action/blob/master/docs/advanced/share-image-jobs.md

Upvotes: 3

ITChap
ITChap

Reputation: 4732

Edit:

As mentioned by Romain in the comments. The initial solution will pull the image at the beginning of the workflow and as such will not use the image that is built during the workflow. The only solution seem to be running docker run yourself in the step:


- name: Run my docker image
  run: >
    docker run -t ivan123123/c_matrix_library:latest
    ...

On a side note. Using this solution might get a bit complicated if you use services in your job. In which case, the networking between your container and the service containers will be troublesome

Original answer:

To run the image you can use the following:


- name: Run my docker image
  uses: docker://ivan123123/c_matrix_library:latest
  with:
    entrypoint: ...
    args: ...

The entrypoint and args are optional. You can find more info here. One limitation though is that you can use any variable or context in the uses field. You can only hardcode the name and tag of the image.

Upvotes: 1

Related Questions