Published
- 6 min read
Streamlit Deployment Guide Part 2: GitHub Actions & ghcr.io
This showcases a GitHub Workflow walkthrough of building and publishing a Docker image to GitHub Container Registry. It continues a series detailing the process of deploying Streamlit app to Azure, broken down into the following parts:
- Part 1: Containerizing a Streamlit app.
- Part 2: GitHub Workflow for Building and Publishing to ghcr.io You are here đ
- Part 3: Azure Infrastructure via Terraform
- Part 4: GitHub Workflow for Terraform Apply & Destroy
Do you want to deploy your Streamlit application on Azure right now? Use the template repository đ
TL;DR
See the completed GitHub Workflow.
Prerequisites
A basic understanding of GitHub Actions is required and the Workflow assumes you have the files from Part 1:
- /app/Dockerfile
- /app/app.py
- /app/requirements.txt
- /app/.streamlit/config.toml
- gitversion.yml
If you have your own Dockerfile, ensure all the dependent files are available and are stored within /app folder for this Workflow to function correctly.
Create a GitHub Workflow
The Workflow file should be stored within the following folder: .github/workflows/
. A concise name could be docker-image-build-and-push.yaml
.
Completed Workflow
name: Docker Image Build and Push
on:
push:
branches: ['main']
paths:
- 'app/**'
workflow_dispatch:
# If this workflow is triggered again while a previous run is still in progress, GitHub will queue the new run until the previous one completes.
concurrency:
group: build-and-push-image
jobs:
build-and-push-image:
runs-on: ubuntu-latest
env:
REGISTRY: ghcr.io
permissions:
contents: write
packages: write
steps:
# Checkout the repository
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
# image name needs to be lowercase, some accounts have uppercase letters
- name: Get owner/repo name and convert to lowercase
id: get-image-name
run: echo "image-name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
# Install GitVersion for versioning
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v1.1.1
with:
versionSpec: '5.x'
# Execute GitVersion to get the version number
- name: Use GitVersion
id: gitversion
uses: gittools/actions/gitversion/execute@v1.1.1
# Set up Git user for tagging
- uses: fregante/setup-git-user@v2
# Create a new tag based on the version number
- name: Create Tag
run: git tag -a ${{ steps.gitversion.outputs.semVer }} -m "Auto-generated tag from GitHub Action."
# Push the newly created tags to the repository
- name: Push Tags
run: git push origin --tags
# Log in to the Container registry
- name: Log in to the Container registry
uses: docker/login-action@v3.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Build and push the Docker image to the registry
- name: Build and push Docker image
uses: docker/build-push-action@v5.3.0
with:
context: ./app
push: true
tags: |
${{ env.REGISTRY }}/${{ steps.get-image-name.outputs.image-name }}:latest
${{ env.REGISTRY }}/${{ steps.get-image-name.outputs.image-name }}:${{ steps.gitversion.outputs.semVer }}
Workflow Walkthrough
Workflow Definition
name: Docker Image Build and Push
The name of the workflow which is shown under the Actions tab. This allows you to set a readable name for your Workflow, if omitted the file name will be shown.
Triggers
on:
push:
branches: ['main']
paths:
- 'app/**'
workflow_dispatch:
The workflow triggers on pushes to the âmainâ branch and only if the changes are made in the âappâ directory. The âworkflow_dispatchâ allows the workflow to be triggered manually.
Concurrency
concurrency:
- group: build-and-push-image
This ensures that if the workflow is triggered again while a previous run is in progress, the new run will queue until the previous one completes.
Jobs
jobs:
build-and-push-image:
runs-on: ubuntu-latest
env:
REGISTRY: ghcr.io
There is a single job defined which covers the full behavior to build and push the image. âruns-onâ specifies it should run on the latest version of Ubuntu. âenvâ allows you to set environment variables available to all tasks within the job. âREGISTRYâ represents the Docker registry and is set to âghcr.ioâ
Permissions
permissions:
contents: write
packages: write
Grants the necessary permissions for the job to interact with the repository. Contents permission is needed to checkout the repository and specifically write is needed to be able to tag the repository. Packages write is needed to publish the Docker image.
Steps
1. Checkout the Repository
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
Checks out the repository to the job runner. Allowing the runner to access the repository content.
2. Get Owner/Repo Name and Convert to Lowercase
- name: Get owner/repo name and convert to lowercase
id: get-image-name
run: echo "image-name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
Retrieves the repository name and converts it to lowercase for use as the Docker image name (lowercase name is required). You can easily use an env variable for your image name. This just allows it to be automated.
3. Install GitVersion for Versioning
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v1.1.1
with:
versionSpec: '5.x'
Installs GitVersion tool for semantic versioning. This tool is used to generate a version for our Docker image. See explanation of GitVersion
4. Execute GitVersion to Get the Version Number
- name: Use GitVersion
id: gitversion
uses: gittools/actions/gitversion/execute@v1.1.1
Runs GitVersion execute to generate the semantic version number. It sets the version number as output and available as steps.gitversion.outputs.semVer
5. Set Up Git User for Tagging
- uses: fregante/setup-git-user@v2
Sets up Git user for tagging purposes.
6. Create a New Tag Based on the Version Number
- name: Create Tag
run: git tag -a ${{ steps.gitversion.outputs.semVer }} -m "Auto-generated tag from GitHub Action."
Creates a new Git tag based on the version number generated by GitVersion.
7. Push the Newly Created Tags to the Repository
- name: Push Tags
run: git push origin --tags
Pushes the created tag to the GitHub repository.
8. Log in to the Container Registry
- name: Log in to the Container registry
uses: docker/login-action@v3.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
Logs into the container registry using the GitHub actor and token.
9. Build and Push the Docker Image to the Registry
- name: Build and push Docker image
uses: docker/build-push-action@v5.3.0
with:
context: ./app
push: true
tags: |
${{ env.REGISTRY }}/${{ steps.get-image-name.outputs.image-name }}:latest
${{ env.REGISTRY }}/${{ steps.get-image-name.outputs.image-name }}:${{ steps.gitversion.outputs.semVer }}
Builds and pushes the Docker image to the specified registry with the appropriate tags. The context is set to the âappâ directory, and the image is tagged with both âlatestâ and the semantic version.
What is GitVersion?
GitVersion is a tool that helps automate versioning of software projects based on Git commit history, providing a consistent and reliable way to generate version numbers for different branches and releases. Read the documentation
Generated Version Number Example
How the version is generated is based on how you set up your GitVersion configuration file. Assume we used the configuration file and start with version 1.0.0 along with using conventional commits. Example commit history and resulting versions:
- fix: resolve login bug -> Generated Version:
1.0.1
- feat: add user profile page -> Generated Version:
1.1.0
Example gitversion.yml Configuration
The provided GitVersion file contains configuration settings for the GitVersion tool, basically it defines the rules on how the version will be generated. See configuration documentation for in-depth explanations.
assembly-versioning-scheme: MajorMinorPatch
assembly-file-versioning-scheme: MajorMinorPatch
assembly-informational-format: '{InformationalVersion}'
mode: ContinuousDelivery
increment: Inherit
continuous-delivery-fallback-tag: ci
tag-prefix: '[vV]'
major-version-bump-message: "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?(!:|:.*\\n\\n((.+\\n)+\\n)?BREAKING CHANGE:\\s.+)"
minor-version-bump-message: "^(feat)(\\([\\w\\s-]*\\))?:"
patch-version-bump-message: "^(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?:"
no-bump-message: '\+semver:\s?(none|skip)'
commit-message-incrementing: Enabled
merge-message-formats: {}
update-build-number: true