Skip to content

Release Pipeline

The three deployable components are packaged as Debian packages and published to GitHub Releases as a flat APT repository. The same component build commands are reused by local development, the container images, and CI.

Components and packages

Directory Package Arch Contents
create-a-container/ opensource-server amd64 Manager web app, job runner, systemd units
mie-opensource-landing/ opensource-docs all Prebuilt documentation site
pull-config/ opensource-agent all pull-config engine, instances, error pages

Everything installs under the /opt/opensource-server prefix, matching the paths referenced by the systemd units, the pull-config instances, and the manager-rendered nginx configuration. opensource-server depends on opensource-agent and opensource-docs because the manager's nginx config serves the agent's error pages and the docs site.

The component Makefile contract

Each component directory has a self-contained Makefile with the same targets. The default goal is help, which lists the available targets.

Target Description
help List the available targets (default goal)
deps Install build/runtime dependencies (npm ci, uv sync, or nothing)
build Compile the component; depends on deps
install Stage built files into DESTDIR at their final paths; depends on build
dev Run the development watch loop; depends on deps
deb / rpm / apk Stage and package with fpm; depend on install

Variables (overridable):

Variable Default Meaning
PREFIX /opt/opensource-server Vendor install prefix
DESTDIR / Staging root for install

The package version is derived from git by ./package-version, which composes a format-appropriate string per packager from git describe --tags --long --dirty: an exact tag 2026.6.3 is used as-is; commits after a tag become a snapshot that sorts above the tag and below the next release (e.g. deb 2026.6.3+<n>.g<sha>); a prerelease tag 2026.6.3-rc1 sorts below the eventual 2026.6.3. A leading v on the tag is optional — it is stripped if present (v2026.6.3 and 2026.6.3 are equivalent).

# Build and stage a component anywhere:
make -C pull-config install DESTDIR=/tmp/agent-root

# Build one package:
make -C create-a-container deb        # -> create-a-container/*.deb

# Build all three and collect them into ./dist:
make deb

The top-level Makefile simply forwards these targets to every component and collects the packages into dist/.

Development

Each component's dev target runs it locally:

make -C create-a-container dev        # Manager on localhost (SQLite, no Proxmox); see the Development Workflow guide
make -C mie-opensource-landing dev    # docs live server

make dev at the repo root delegates to create-a-container.

Packaging with fpm

Each component has a .fpm options file holding the static package metadata (name, architecture, dependencies, description, scripts, config files). The Makefile's package target stages the component into a .pkg/buildroot and runs fpm with the dynamic options on the command line — output type, version (composed per format by ./package-version), and the staging dir. fpm's dir input copies the staged tree verbatim from -C .pkg/buildroot, preserving symlinks (e.g. node_modules/.bin/sequelize) and the directory layout. The same definition produces deb, rpm, and apk, so make rpm and make apk also work.

  • opensource-server ships the container-creator and job-runner systemd units and enables them via an after-install script (before-remove disables them on real removal). The log directory is created on demand by the unit's LogsDirectory, not shipped in the package. The logrotate drop-in is the only config file. The container-creator-init unit (which provisions a local PostgreSQL) is not in the package — it is part of the manager image, since the package only suggests postgresql and works with a remote database too.
  • opensource-agent ships the pull-config engine, instances, cron schedule and the static error pages. These are program code, not configuration (runtime config comes from /etc/environment), so nothing is marked as a config file.
  • opensource-docs ships content only.

Config files are marked explicitly with --config-files; --deb-no-default-config-files stops fpm/dpkg from auto-marking everything under /etc as a conffile.

Container images

The builder image runs make deb (Node.js, uv, and fpm) to produce all three packages, exported as an artifact-only stage. The docs, agent, and manager images install those packages via a Docker Bake builder context instead of copying the repository. The packages are bind-mounted (no extra image layer):

RUN --mount=from=builder,source=/dist,target=/dist \
    apt-get update && \
    apt-get install -y /dist/opensource-agent_*.deb

Each leaf image also stages the release APT source (/etc/apt/sources.list.d/opensource-server.sources) so a running container picks up future releases with apt upgrade. The source stays in place for the whole build which is normally not an issue since the local builder would have built a newer version than what's available in the repo.

Releases

To cut a release, publish a GitHub release (full or prerelease) for a semver tag — the leading v is optional (e.g. 2026.6.3 or v2026.6.3, and 2026.6.3-rc1 for a prerelease). Publishing the release triggers release.yml, which builds the packages, generates flat APT repository metadata (Packages, Packages.gz) with dpkg-scanpackages, and uploads the debs and metadata to that release. The workflow never creates or modifies the release itself — you choose full vs prerelease when creating it. Because GitHub serves releases/latest/download/<file> for the newest non-prerelease release, the release doubles as an apt source:

deb [trusted=yes] https://github.com/mieweb/opensource-server/releases/latest/download/ ./

Image tagging follows the same rule: :latest is published only when a non-prerelease release is published, keeping the :latest image channel aligned with the releases/latest package channel. Pre-releases publish their own assets and :X.Y.Z image tags without moving :latest.

Installing and updating on a host

# One-off install (stable URL):
curl -fsSLO https://github.com/mieweb/opensource-server/releases/latest/download/opensource-agent_latest.deb
apt install -y ./opensource-agent_latest.deb

# Or add the apt source once, then use apt normally:
cat >/etc/apt/sources.list.d/opensource-server.sources <<'EOF'
Types: deb
URIs: https://github.com/mieweb/opensource-server/releases/latest/download/
Suites: ./
Trusted: yes
EOF
apt update && apt install opensource-server