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. Public mittwald GitHub actions take care of the API calls, apply your stack definition, and (if necessary) re-create the affected services.
Prerequisites
- mStudio API token; see the docs on obtaining an API token
- GitHub Actions runner; any runner with Docker support, e.g.
ubuntu-latest
Which action should you use?
You now have two different deployment styles:
| Use case | Recommended action | Why |
|---|---|---|
| You want to deploy quickly from source code without creating Dockerfiles or stack files first | mittwald/zerodeploy-action | Uses Railpack to infer build steps and deploys directly to mittwald container hosting with near-zero config. |
You already have a Dockerfile and/or explicit stack definition (stack.yaml) and want full control over services, ports, and rollout behavior | mittwald/deploy-container-action | Best when you want an explicit, infrastructure-focused deployment model. |
If you want the fastest path from repository to running service, use zerodeploy-action. If your team already maintains container build and runtime definitions, stay with deploy-container-action.
Deploy with mittwald/zerodeploy-action
The mittwald/zerodeploy-action is designed for repositories that do not have a Docker setup yet. It automatically determines the best way to build a Docker image from your code, and deploys that to your target project, using a dynamically provisioned container registry.
Required inputs
MITTWALD_API_TOKENsecret; API token from mStudio- Project ID (for example
p-XXXXXX)
Example workflow
Create .github/workflows/zerodeploy.yml:
name: Deploy to mittwald Container Hosting
on:
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create .env for deployment
run: |
{
echo "APP_ENV=production"
echo "APP_SECRET=${{ secrets.APP_SECRET }}"
} > .env
- name: Deploy with zerodeploy
uses: mittwald/zerodeploy-action@v1
with:
mittwald-api-token: ${{ secrets.MITTWALD_API_TOKEN }}
mittwald-project-id: "p-XXXXXX"
The action automatically checks for .env in $GITHUB_WORKSPACE/.env and includes it for deployment when present.
Deploy with mittwald/deploy-container-action
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 for mittwald/deploy-container-action
- Stack ID; see the docs on identifying the default stack
- (Optional) Private registry; see Using private registries
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 or (when you're deploying into multiple environments) using a GitHub environment secret, 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: "{{ .Env.IMAGE_TAG }}"
description: "My web app"
ports:
- "8080/tcp"
environment:
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:
tags: [v*]
# alternatively:
# push:
# branches: [main]
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 }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build & push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy to mStudio
uses: mittwald/deploy-container-action@v1
env:
IMAGE_TAG: ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
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 of a new Git tag (or on any push to the main branch, 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 for mittwald/deploy-container-action
The following options apply to mittwald/deploy-container-action (the stack-based workflow), not to mittwald/zerodeploy-action.
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.