Deploying containerized applications with GitHub Actions
Using GitHub Actions you can build and publish your container image right from your source code repository, and then immediately deploy your application on the mittwald cloud platform. The official mittwald/deploy-container-action
takes care of the API calls, applies your stack definition, and (if necessary) re-creates the affected services.
Prerequisites
- mStudio API token; see the docs on obtaining an API token
- Stack ID; see the docs on identifying the default stack
- (Optional) Private registry; see Using private registries
- GitHub Actions runner; any runner with Docker support, e.g.
ubuntu-latest
Identify your stack
Follow the documentation on identifying the default stack of your project. For convenience, we recommend storing the stack ID in a GitHub variable, e.g. STACK_ID
.
Write your stack.yaml
Next, create a file containing your stack definition somewhere in your repository (for this example, we'll assume deploy/stack.yaml
). This file should follow the same format as the PUT/
operation.
services:
app:
image: ghcr.io/<OWNER>/<REPO>:{{ .Env.IMAGE_TAG }}
description: "My web app"
ports:
- "8080/tcp"
envs:
APP_ENV: production
volumes: {}
The placeholders inside {{ … }}
are resolved from environment variables you pass in the workflow, so your secrets stay in GitHub and never touch the repo.
Add secrets and variable to the repo
MITTWALD_API_TOKEN
; API token from mStudioSTACK_ID
; the ID copied in step 1. You could set this as a simple configuration variable, but might also attach this variable to your target environment, if you want to deploy into multiple stacks, depending on your environment.
Create the GitHub Action workflow
Create .github/workflows/deploy.yml
:
name: Build & Deploy to mittwald
on:
push:
branches: [main]
# alternatively:
# push:
# tags: [v*]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
environment:
name: production
url: https://your-app.example
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
- name: Build & push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Deploy to mStudio
uses: mittwald/deploy-container-action@v1
env:
IMAGE_TAG: ${{ github.sha }}
with:
api_token: ${{ secrets.MITTWALD_API_TOKEN }}
stack_id: ${{ vars.STACK_ID }}
stack_file: "${{ github.workspace }}/configs/stack.yaml"
Using this workflow, on every push to main
(or on any new Git tag, depending on which configuration you choose), GitHub will build a new image, push it to GitHub Container Registry and instruct mStudio to run it.
Advanced patterns
Selective restarts
If you need to avoid downtime for stateful services (e.g. databases) you can tell the Action to skip recreation after the update:
with:
skip_recreation: "mysql,redis"
Services not listed are only restarted when the API reports that a restart is actually required.