Dynamically build containers in a monolithic Github repository and upload them to ghcr.
Sometimes, repositories are used to centralize multiple service, either applications such as microservices or more simply, different application components.
This repository template allows to dynamically build and publish the corresponding Container Images (or "Docker Image") to Github's Container Registry.
Everything it configured in a single Github Actions Workflow located in the
./.github/workflows/main.yaml
file.
This is achieved using the following pattern:
- The
matrix
job (Generate the matrix
) finds every directory containing a Dockerfile. It then creates a list oftarget
by using thegenerate_matrix.sh
script and outputs a JSON string that defines the matrix strategy that will be used downstream. - The
check-matrix
(Validate and display matrix
) job ensure that the output is using the proper JSON format that can be injected in the Github Actions Workflow - The main course. The
build-containers
job uses the matrix definition created in thematrix
job to start N (where N is the number of targets) parallel jobs that will build each service and upload it to the ghcr repository matching the current Github Repository. Each image tag is prefixed by a slug of the used path to automatically apply a naming convention. The current Github Reference slug is used as the suffix (i.e.: branch, tag)
Note: this can be adapted by modifying the generate_matrix.sh
script in the
top-level directory of the repository.
Each directory containing a Dockerfile
is used as a target
.
Considering that we have 2 targets structured as the following:
$ tree demo/
demo/
├── app1
│ └── Dockerfile
└── app2
└── Dockerfile
We will end up with a matrix with a structure matching the one below:
target:
- src: ./demo/app2
name: ./demo/app2/Dockerfile
- src: ./demo/app1
name: ./demo/app1/Dockerfile
It is then exposed as an output in a single-line JSON using yq
.
Pretty printed JSON:
{
"target": [
{
"src": "./demo/app2",
"name": "./demo/app2/Dockerfile"
},
{
"src": "./demo/app1",
"name": "./demo/app1/Dockerfile"
}
]
}
Single-line JSON:
{"target":[{"src":"./demo/app2","name":"./demo/app2/Dockerfile"},{"src":"./demo/app1","name":"./demo/app1/Dockerfile"}]}
This repository includes a ./demo
directory that acts as a dummy
implementation of this Workflow.
Once rendered, the Github Actions Workflow "Build Containers" creates the
following container images when running against the main
branch:
ghcr.io/tbobm/gha-dynamic-containers:demo-app1-main
ghcr.io/tbobm/gha-dynamic-containers:demo-app2-main
Github Actions Workflow can be dynamically adapted using the fromJson
function that will inject the string directly in the Workflow.
This is configured in the build-containers
attribute, as shown below:
build-containers:
runs-on: "ubuntu-latest"
name: "Build and push OCIs"
needs:
- matrix
strategy:
matrix: ${{fromJson(needs.matrix.outputs.matrix)}}
Using the demo
example, we will end up with 2 build-containers
jobs:
./demo/app1
./demo/app2
Both of them resulting in a different images based on the OCI tag.
Once rendered using the dynamic matrix, the Workflow looks like the following:
build-containers:
runs-on: "ubuntu-latest"
name: "Build and push OCIs"
needs:
- matrix
strategy:
matrix:
target:
- src: ./demo/app2
name: ./demo/app2/Dockerfile
- src: ./demo/app1
name: ./demo/app1/Dockerfile
Simply create your new repository using the Use this template
button
available in this repository.
Everything is pretty much bootstrapped to work out of the box thanks to the github-slug-action that injects the current repository name as the registry entry in ghcr.
For more information see Creating a Repository from a Template in the Github documentation.
Once done, you can create your own directory structure that will be easy to use in your existing workflow.