Docker-based build environment for linuxmuster.net Debian packages.
The image can be used both as a container in a GitHub Actions workflow and as a local build system on a Linux machine.
- Prerequisites
- Directory Structure
- Configuration
- Building the Image
- Local Usage
- Usage as a GitHub Actions Runner
- Release Workflows
- Per-Project Build Scripts
- Publishing the Image (GHCR)
For local use the following are required:
- Docker
bash,curl,python3(forcollect-deps.shandbuild.sh)- Internet access (GitHub API, Ubuntu package repositories)
For GitHub Actions use no local prerequisites are needed — the image is pulled from GHCR.
lmndev-runner/
├── config # Central configuration
├── Dockerfile # Image definition
├── build.sh # Local image build
├── collect-deps.sh # Collects build dependencies, downloads linuxmuster-common
├── start.sh # Starts the container interactively
├── build/ # Per-project build scripts (installed in image at /opt/lmndev/build/)
│ ├── common.sh # Shared build functions
│ ├── linuxmuster-api.sh
│ ├── linuxmuster-base7.sh
│ ├── linuxmuster-cli7.sh
│ ├── linuxmuster-common.sh
│ ├── linuxmuster-fileserver.sh
│ ├── linuxmuster-linbo7.sh
│ ├── linuxmuster-linbo-gui.sh
│ ├── linuxmuster-linuxclient7.sh
│ ├── linuxmuster-prepare.sh
│ ├── linuxmuster-tools.sh
│ ├── linuxmuster-webui7.sh
│ └── sophomorix4.sh
├── workflows/ # Sample release workflows for project repositories
│ ├── linuxmuster-api.release.yml
│ ├── linuxmuster-base7.release.yml
│ └── ... # one .release.yml per project
└── .github/workflows/
└── build-push.yml # Builds and pushes the image itself to GHCR
All settings are located in the config file. It is sourced by build.sh,
collect-deps.sh, and start.sh.
# Ubuntu version of the base image
UBUNTU_VERSION="26.04"
UBUNTU_FALLBACK="24.04" # fallback if the primary version is not available
# Docker image names
IMAGE_NAME="lmndev-runner"
GHCR_IMAGE="ghcr.io/linuxmuster/lmndev-runner"
# Default shell in the container: bash | zsh | ash | fish
DEFAULT_SHELL="bash"
# Additional packages for local use only (leave empty for GitHub Actions)
EXTRA_PACKAGES=""
# Build user in the container
MY_USER="build"
MY_UID="1000"
MY_GID="1000"
# Supported projects (space-separated)
PROJECTS="linuxmuster-api linuxmuster-base7 ..."| Parameter | Description | Default |
|---|---|---|
UBUNTU_VERSION |
Ubuntu base version | 26.04 |
UBUNTU_FALLBACK |
Fallback version | 24.04 |
DEFAULT_SHELL |
Default shell when starting start.sh |
bash |
EXTRA_PACKAGES |
Overrides the Dockerfile's default extra packages | (empty = Dockerfile defaults are used) |
MY_UID / MY_GID |
UID/GID of the build user | 1000 / 1000 |
PROJECTS |
Projects to support | all 12 projects |
The Dockerfile already ships a sensible default set of extra packages:
openssh-client iputils-ping net-tools wget bash bash-completion zsh
zsh-autosuggestions zsh-syntax-highlighting ccache distcc curl
dpkg-dev debdelta sudo vim git
When EXTRA_PACKAGES is empty in config, these Dockerfile defaults are used.
When EXTRA_PACKAGES is set in config, it replaces the Dockerfile defaults entirely.
All four shells are always installed in the image — regardless of configuration. The desired shell is selected at runtime only.
| Value | Package | Shell Path |
|---|---|---|
bash |
bash |
/bin/bash |
zsh |
zsh |
/bin/zsh |
ash |
busybox |
/bin/ash |
fish |
fish |
/usr/bin/fish |
To support only specific projects, shorten the PROJECTS variable in config
accordingly. Build dependencies will then only be collected for the listed projects,
reducing the image size.
./build.shThis command automatically performs the following steps:
- Check the Ubuntu version — if
ubuntu:26.04is not available, it falls back toubuntu:24.04. - Call
collect-deps.sh— fetches thedebian/controlfile for each configured project from GitHub and extracts allBuild-Dependsfields. The deduplicated package list is written todeps.txt. - Download the latest
linuxmuster-common— the most recent.debpackage is determined via the GitHub Releases API and saved locally. - Run
docker build— the image is built with the configured build arguments. The temporary files (deps.txt,linuxmuster-common.deb) are deleted afterwards.
If deps.txt and linuxmuster-common.deb are already present (e.g. after a failed
build), the download step can be skipped:
./build.sh --no-collectThe script downloads the debian/control file for each project listed in PROJECTS
directly from GitHub:
https://raw.githubusercontent.com/linuxmuster/PROJECT/main/debian/control
All package names are extracted from the Build-Depends field. During this process:
- Version constraints (e.g.
debhelper (>= 10)) are stripped - Alternatives (
package-a | package-b) are reduced to the first option linuxmuster-*packages are excluded (installed separately)- Duplicates are removed
The linuxmuster-common package is fetched separately via the GitHub Releases API
and installed with gdebi to automatically resolve its dependencies.
./start.sh [source-directory] [shell] [home-directory]| Argument | Description | Default |
|---|---|---|
source-directory |
Mounted as /workspace |
current directory |
shell |
Shell in the container: bash, zsh, ash, fish |
DEFAULT_SHELL from config |
home-directory |
Host directory mounted as /home/build |
none |
The shell can also be set via the DEFAULT_SHELL environment variable.
The home directory can alternatively be passed via BUILD_HOME.
Examples:
# Simple start with default shell
cd ~/src/linuxmuster-base7
/path/to/lmndev-runner/start.sh .
# Use zsh as shell
/path/to/lmndev-runner/start.sh ~/src/linuxmuster-base7 zsh
# With a custom home directory (shell config, history, …)
/path/to/lmndev-runner/start.sh ~/src/linuxmuster-base7 zsh ~/lmndev-home
# Shell via environment variable
DEFAULT_SHELL=fish /path/to/lmndev-runner/start.sh ~/src/linuxmuster-base7
# Home directory via environment variable
BUILD_HOME=~/lmndev-home /path/to/lmndev-runner/start.sh ~/src/linuxmuster-base7# Inside the container:
linuxmuster-base7.shdocker run --rm \
-v "$PWD:/workspace" \
-w /workspace \
lmndev-runner:latest \
/opt/lmndev/build/linuxmuster-base7.shThe environment variable OUTPUT_DIR causes the generated .deb files to be moved
to a specific directory:
docker run --rm \
-v "$PWD:/workspace" \
-v "$HOME/packages:/output" \
-w /workspace \
-e OUTPUT_DIR=/output \
lmndev-runner:latest \
/opt/lmndev/build/linuxmuster-base7.shThe image can be used as a job container in any GitHub Actions workflow. All build dependencies of the supported projects are already included in the image.
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/linuxmuster/lmndev-runner:latest
options: --user root
steps:
- uses: actions/checkout@v4
- run: /opt/lmndev/build/linuxmuster-base7.sh
env:
OUTPUT_DIR: ${{ github.workspace }}/packagesThe build scripts are installed in the container at /opt/lmndev/build/ and can be
called directly by name via $PATH.
For each supported project a ready-to-use sample file PROJECT.release.yml is
provided in the workflows/ directory. It can be copied directly into the respective
project repository and used as a GitHub Actions release workflow.
-
Copy the matching file from
workflows/into the project repository:cp workflows/linuxmuster-base7.release.yml \ ~/src/linuxmuster-base7/.github/workflows/release.yml -
Add three secrets to the project repository (
Settings → Secrets and variables → Actions):Secret Description REPO_HOSTHostname of the APT repository server REPO_USERSSH username on the server REPO_SSH_KEYPrivate SSH key (without passphrase) -
Push a release tag — the workflow starts automatically:
git tag v7.3.5-0 git push origin v7.3.5-0
The workflow consists of three sequential jobs:
build ──→ release ──→ publish
| Job | Description |
|---|---|
| build | Builds the package inside the lmndev-runner container. Version and distribution are read automatically from the first line of debian/changelog (e.g. linuxmuster-base7 (7.3.5-0) lmn73; urgency=medium). |
| release | Creates a GitHub Release with the generated .deb files. |
| publish | Transfers the packages to the APT repository server via SSH and updates the repository index (repo-update lmn73 or repo-update lmn74). |
The target distribution (lmn73 or lmn74) is determined automatically from
debian/changelog — no manual selection is required.
The workflow triggers on tags matching the pattern v[0-9]*, for example:
v7.3.5-0
v7.4.0-1
Each project has its own build script at build/PROJECT.sh. Inside the container
these are installed at /opt/lmndev/build/ and can be called directly by name.
All scripts use the shared build_package() function from build/common.sh.
| Variable | Description | Default |
|---|---|---|
WORKSPACE |
Source directory | $GITHUB_WORKSPACE or current directory |
OUTPUT_DIR |
Target directory for .deb files |
(unset, files remain in parent directory) |
BUILD_FLAGS |
Additional dpkg-buildpackage flags |
(empty) |
CCACHE_DIR |
ccache directory | /tmp/ccache |
ccache is activated automatically when available in the container.
The Linbo build is very resource-intensive (kernel, network drivers, Busybox). The
script build/linuxmuster-linbo7.sh automatically sets:
MAKEFLAGS="-j$(nproc)"
DEB_BUILD_OPTIONS="parallel=$(nproc)"The included workflow .github/workflows/build-push.yml automatically builds the
image on every push to main and pushes it to:
ghcr.io/linuxmuster/lmndev-runner:latest
In the lmndev-runner repository under Settings → Actions → General, the following
must be set:
Workflow permissions: Read and write permissions
This allows the workflow to write the image to the GitHub Container Registry.
# Build locally
./build.sh
# Push manually (after gh auth login or docker login ghcr.io)
docker push ghcr.io/linuxmuster/lmndev-runner:latestThis project is part of the linuxmuster.net development environment.