Seller Guide

Automated Builds

Automate Docker image builds with GitHub Actions and other CI/CD systems.

Automate your Docker builds so every release is pushed to the Self-Host Pro registry without manual intervention.

See it in action — Our demo repository has production-ready GitHub Actions workflows you can copy and adapt. The demo uses Laravel, but Self-Host Pro works with any containerized application — Node.js, Python, Go, or any stack that runs in Docker.

Release strategy

A common pattern uses three release channels:

ChannelTriggerTagsUse case
EdgePush to mainedge-mainContinuous deployment for testing
PrereleaseGitHub prereleaseprerelease, prerelease-v1.0.0-beta.1Beta testing with select customers
StableGitHub releaselatest, 1.2.0, 1Production releases

This gives you flexibility — push to main for instant edge builds, create a prerelease for beta feedback, then publish a stable release when ready.

Reusable workflow pattern

Instead of duplicating build logic, create a reusable workflow that handles the actual build, then call it from simple trigger workflows.

Build workflow

This workflow handles dependency caching, asset compilation, and pushing to the registry:

.github/workflows/service_docker-build-and-publish.yml
name: Docker Build & Publish

on:
  workflow_call:
    inputs:
      docker-tags:
        required: true
        type: string
        description: "Comma or newline separated list of Docker tags"
      dockerfile:
        type: string
        default: "./Dockerfile"
      environment:
        type: string
        required: true
      version:
        type: string
        required: false

jobs:
  docker-publish:
    runs-on: ubuntu-24.04
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # Add your build steps here (dependency install, asset compilation, etc.)

      - name: Build and push
        uses: serversideup/github-action-docker-build@v6
        with:
          tags: ${{ inputs.docker-tags }}
          dockerfile: ${{ inputs.dockerfile }}
          registry: "shpcr.io"
          registry-username: ${{ secrets.SHPCR_USERNAME }}
          registry-password: ${{ secrets.SHPCR_PASSWORD }}

Trigger workflows

Each release channel gets a simple workflow that calls the build workflow with appropriate tags. Replace your-org/your-app with your actual image path from the Docker Registry.

name: Edge Release

on:
  push:
    branches:
      - main

jobs:
  build:
    uses: ./.github/workflows/service_docker-build-and-publish.yml
    with:
      docker-tags: "shpcr.io/your-org/your-app:edge-${{ github.ref_name }}"
      environment: edge
      version: "edge-${{ github.ref_name }}"
    secrets: inherit

How stable release versioning works

The stable release workflow is split into two jobs because GitHub Actions can't parse outputs and use them in the same job — the prepare job runs first, then passes values to build.

When you publish a GitHub release tagged v1.2.0, here's what happens:

  1. github.ref_name gives you the raw tag: v1.2.0
  2. VERSION="${VERSION#v}" strips the v prefix → 1.2.0
  3. MAJOR="${VERSION%%.*}" extracts everything before the first dot → 1
  4. Those two values are passed as outputs to the build job

The result is three tags pushed to the registry in one build:

TagValuePurpose
latestAlways latestDefault pull for new installs
1.2.0Full semverPin to an exact release
1Major version onlyStay on major, get patch updates

Customers pinned to 1 automatically receive 1.2.0, 1.3.0, etc. — but not a breaking 2.0.0. Customers who need stability can pin to 1.2.0 and update manually.

Required secrets

In your GitHub repo, go to Settings → Secrets and variables → Actions and add:

SecretValue
SHPCR_USERNAMEYour Self-Host Pro email
SHPCR_PASSWORDYour team access token

See Docker Registry for how to generate an access token.

Version embedding

Pass version from the trigger workflow to embed the release version into your image at build time. In the reusable build workflow, use it to stamp your app's version file before the Docker build step:

- name: Embed version
  if: inputs.version != ''
  run: |
    jq '.version = "${{ inputs.version }}"' composer.json > composer.json.tmp
    mv composer.json.tmp composer.json

The example above is Laravel-specific (composer.json). Adapt it for your stack:

StackWhere to write the version
Node.jspackage.json via jq
PythonA VERSION file or pyproject.toml
GoA version.go constant or ldflags at build time
AnyA plain VERSION file your app reads at startup

Other CI/CD systems

The same pattern works with any CI/CD system — GitLab CI, CircleCI, Jenkins, etc. The core steps are:

  1. Log in to the registry with docker login shpcr.io
  2. Build your image
  3. Tag with appropriate version(s)
  4. Push to shpcr.io

The workflows above are just orchestration around standard Docker commands.