Github Actions Docker Pipeline
Something that I have been using more and more is Github Actions. It is an easy to use tool to run tests and build in CI. One of the nice things about it is that is is more module than GitLab Pipeline. There is also a marketplace of company and user created actions so there are many options to choose when you are looking to build something out.
I have used them to build docker pipelines for container building and deployment. I found that actions workflow were great to keep a simple and clean workflow for my pypy-flask docker image. The full workflow file is available in the repo but I will be breaking it down below.
Full file:
1name: Docker
2
3on:
4 push:
5
6
7jobs:
8 Slim:
9 runs-on: ubuntu-latest
10 steps:
11
12 - name: Checkout
13 uses: actions/checkout@v2
14
15 - name: Login to Docker
16 uses: docker/login-action@v1
17 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
18 with:
19 username: ${{ secrets.DOCKER_USERNAME }}
20 password: ${{ secrets.DOCKER_PASSWORD }}
21
22 - name: Login To GitHub
23 uses: docker/login-action@v1
24 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
25 with:
26 registry: ghcr.io
27 username: ${{ github.repository_owner }}
28 password: ${{ secrets.CR_PAT }}
29
30 - name: Login To GitLab
31 uses: docker/login-action@v1
32 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
33 with:
34 registry: registry.gitlab.com
35 username: ${{ secrets.GITLAB_USER }}
36 password: ${{ secrets.GITLAB_TOKEN }}
37
38 - name: Docker Meta
39 id: meta
40 uses: docker/metadata-action@v3
41 with:
42 images: cyb3rjak3/pypy-flask,ghcr.io/cyb3r-jak3/pypy-flask,registry.gitlab.com/cyb3r-jak3/pypy-flask
43 tags: |
44 type=ref,event=pr
45 type=semver,pattern={{version}}
46 type=semver,pattern={{major}}.{{minor}}
47 type=sha
48 labels: |
49 org.label-schema.vcs-url=https://github.com/Cyb3r-Jak3/pypy-flask.git
50 org.label-schema.schema-version=1.0.0-rc1
51
52 - name: Set up QEMU
53 uses: docker/[email protected]
54
55 - name: Set up Docker Buildx
56 uses: docker/[email protected]
57
58 - name: Cache Docker layers
59 uses: actions/[email protected]
60 with:
61 path: /tmp/.buildx-cache
62 key: buildx-slim-${{ github.sha }}
63 restore-keys: buildx-slim
64
65 - name: Slim Build and Push
66 uses: docker/[email protected]
67 with:
68 platforms: linux/amd64,linux/arm64
69 cache-from: type=local,src=https://blog.cyberjake.xyz/tmp/.buildx-cache
70 cache-to: type=local,dest=/tmp/.buildx-cache
71 push: ${{ startsWith(github.ref, 'refs/tags/v') }}
72 file: Dockerfile
73 tags: ${{ steps.meta.outputs.tags }}
74 labels: ${{ steps.meta.outputs.labels }}
75
76 Alpine:
77 runs-on: ubuntu-latest
78 steps:
79
80 - name: Checkout
81 uses: actions/checkout@v2
82
83 - name: Login to Docker
84 uses: docker/login-action@v1
85 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
86 with:
87 username: ${{ secrets.DOCKER_USERNAME }}
88 password: ${{ secrets.DOCKER_PASSWORD }}
89
90 - name: Login To GitHub
91 uses: docker/login-action@v1
92 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
93 with:
94 registry: ghcr.io
95 username: ${{ github.repository_owner }}
96 password: ${{ secrets.CR_PAT }}
97
98 - name: Login To GitLab
99 uses: docker/login-action@v1
100 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
101 with:
102 registry: registry.gitlab.com
103 username: ${{ secrets.GITLAB_USER }}
104 password: ${{ secrets.GITLAB_TOKEN }}
105
106 - name: Docker Meta
107 id: meta
108 uses: docker/metadata-action@v3
109 with:
110 images: cyb3rjak3/pypy-flask,ghcr.io/cyb3r-jak3/pypy-flask,registry.gitlab.com/cyb3r-jak3/pypy-flask
111 flavor: |
112 suffix=-alpine
113 tags: |
114 type=ref,event=pr
115 type=semver,pattern={{version}}
116 type=semver,pattern={{major}}.{{minor}}
117 type=sha
118 labels: |
119 org.label-schema.vcs-url=https://github.com/Cyb3r-Jak3/pypy-flask.git
120 org.label-schema.schema-version=1.0.0-rc1
121
122 - name: Set up QEMU
123 uses: docker/[email protected]
124
125 - name: Set up Docker Buildx
126 uses: docker/[email protected]
127
128 - name: Cache Docker layers
129 uses: actions/[email protected]
130 with:
131 path: /tmp/.buildx-cache
132 key: buildx-alpine-${{ github.sha }}
133 restore-keys: buildx-alpine
134
135 - name: Alpine Build and Push
136 uses: docker/[email protected]
137 with:
138 platforms: linux/amd64,linux/arm64
139 cache-from: type=local,src=https://blog.cyberjake.xyz/tmp/.buildx-cache
140 cache-to: type=local,dest=/tmp/.buildx-cache
141 push: ${{ startsWith(github.ref, 'refs/tags/v') }}
142 file: alpine.Dockerfile
143 tags: ${{ steps.meta.outputs.tags }}
144 labels: ${{ steps.meta.outputs.labels }}
It looks like a lot but the same job is close to being repeated twice to have better caching and means they run in parallel.
Breaking it down
Main Step
1
2# Name of the Workflow
3name: Docker
4
5# When to run the workflow
6on:
7 # Run on all push
8 push:
9
10# List of the jobs
11jobs:
12 # Name of the job
13 Slim:
14 # Operating system to run. Can be ubuntu, windows or mac
15 runs-on: ubuntu-latest
16 # List of steps for the job
17 steps:
After setting up the workflow there are some starter steps for checking out the repo and logging in.
Where there is uses
in a step it means that the step is using a custom workflow.
1 # Checkout the repo
2 - name: Checkout
3 uses: actions/checkout@v2
4
5 # Log into DockerHub
6 - name: Login to Docker
7 uses: docker/login-action@v1
8 # The IF statement means that only tags that start with v will run this step
9 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
10 # With passes action specific input. Used here with the login creds for DockerHub
11 with:
12 username: ${{ secrets.DOCKER_USERNAME }}
13 password: ${{ secrets.DOCKER_PASSWORD }}
14
15 # Login into GitHub's container registry
16 - name: Login To GitHub
17 uses: docker/login-action@v1
18 # The IF statement means that only tags that start with v will run this step
19 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
20 # With passes action specific input. Used here with the login creds for GitHub
21 with:
22 registry: ghcr.io
23 username: ${{ github.repository_owner }}
24 password: ${{ secrets.CR_PAT }}
25
26 # Login in GitLab's Container registry
27 - name: Login To GitLab
28 uses: docker/login-action@v1
29 # The IF statement means that only tags that start with v will run this step
30 if: ${{ startsWith(github.ref, 'refs/tags/v') }}
31 # With passes action specific input. Used here with the login creds for GitLab
32 with:
33 registry: registry.gitlab.com
34 username: ${{ secrets.GITLAB_USER }}
35 password: ${{ secrets.GITLAB_TOKEN }}
Next the metadata is setup for building the container
1
2 - name: Docker Meta
3 # ID allows us to use the output of the step in later steps
4 id: meta
5 uses: docker/metadata-action@v3
6 # With passes action specific input. Used here to pass the baseline images, tags to generate and labels to add
7 with:
8 images: cyb3rjak3/pypy-flask,ghcr.io/cyb3r-jak3/pypy-flask,registry.gitlab.com/cyb3r-jak3/pypy-flask
9 # The '|' allows for multi line entries.
10 tags: |
11 type=ref,event=pr
12 type=semver,pattern={{version}}
13 type=semver,pattern={{major}}.{{minor}}
14 type=sha
15 labels: |
16 org.label-schema.vcs-url=https://github.com/Cyb3r-Jak3/pypy-flask.git
17 org.label-schema.schema-version=1.0.0-rc1
Once all the metadata has been generated then the build environment is setup
1
2 - name: Set up QEMU
3 # Using docker action
4 uses: docker/[email protected]
5
6 - name: Set up Docker Buildx
7 uses: docker/[email protected]
8
9 # Using the caching action means we can pass items between workflow runs to speed up runs where not much has changed
10 - name: Cache Docker layers
11 # Using docker action
12 uses: actions/[email protected]
13 with:
14 # Path of the files to cache / restore to
15 path: /tmp/.buildx-cache
16 # The cache key to save the files as
17 key: buildx-slim-${{ github.sha }}
18 # The cache key to restore with. It will restore from any key that starts with buildx-slim
19 restore-keys: buildx-slim
Finally the build step
1 - name: Slim Build and Push
2 uses: docker/[email protected]
3 with:
4 # Using buildx to build on multiple platforms
5 platforms: linux/amd64,linux/arm64
6 # Using the cached layers
7 cache-from: type=local,src=https://blog.cyberjake.xyz/tmp/.buildx-cache
8 # Saving layers to cache
9 cache-to: type=local,dest=/tmp/.buildx-cache
10 # Only push to the registries if the git ref is a tag that starts with v
11 push: ${{ startsWith(github.ref, 'refs/tags/v') }}
12 # The Dockerfile to use when building
13 file: Dockerfile
14 # The tags for the container. Used from the meta step
15 tags: ${{ steps.meta.outputs.tags }}
16 # The labels for the container. Used from the meta step
17 labels: ${{ steps.meta.outputs.labels }}
There is a repeat job that runs for the alpine Dockerfile in the repo only adding the suffix of alpine.
Wrap up
Using custom actions means that it is much simpler to build out workflows. I don't have to worry about generating all the tags or labels for each image. It also means that it is easier to copy and paste between repos as only a few lines need to be changed. I highly recommend Github actions for people who are just starting with CI/CD solutions
Further Reading
Actions used
In order of use