Install through an Aetheus pipeline

Once you run one Aetheus, it can install the next one. This page describes the smallest pipeline that does the job: build, deploy, record a release. No QA, no quality gate, no security scan.

This is a second installation, not the first

You need a working Aetheus to run the pipeline. Install the first one with Docker or from binaries.

What you need before writing any YAML

  1. A project

    Everything else hangs off it: the repository, the pipeline, the releases and the artefacts.

  2. A Git repository linked to that project

    The pipeline definition is read from the repository at every launch, and the run checks out a pinned revision from it. A pipeline that needs a workspace is refused before the run is even created if its source repository is missing or ambiguous, so link it explicitly when the project holds more than one.

  3. An online agent on the target host

    The pipeline runs on the machine you are deploying to. Register that machine as a server and let its agent come online first; a stage with no eligible runner waits rather than deploying somewhere else.

    The host also needs whatever the build uses. For the layout below, that is Docker with the Compose plugin.

  4. A variable library for the non-secret values

    Host names, ports, web roots, deployment paths. A pipeline only sees a library it lists explicitly, so declaring it in the YAML is part of the setup, not an afterthought.

  5. A vault for the secrets

    Database password, JWT key, encryption key and salt, initial admin password. A vault the pipeline cannot resolve fails the run, which is the behaviour you want: the alternative would be deploying with blank secrets.

The minimal pipeline

Commit this as .pipeline/aetheus-install.yaml in the linked repository. Three stages: build the images, hand the payload to the deploy stage, then record what was published.

name: aetheus-install
trigger: manual

variable_libraries:
  - aetheus-target-host

vaults:
  - aetheus-target-secrets

variables:
  APP_VERSION: "1.0.$(BUILD_PIPELINE_RUNNUMBER)"
  COMPOSE_PROJECT: "aetheus"
  DEPLOY_DIR: "/srv/aetheus"

stages:
  - name: Build
    os: linux
    group: Deploy
    steps:
      - name: Build the production images
        checkout: true
        working_directory: "$(WORKSPACE)"
        timeout_seconds: 3600
        shell: |
          set -eu
          SOURCE_COMMIT="$(git rev-parse HEAD)"
          docker build \
            --load \
            --build-arg "APP_VERSION=$(APP_VERSION)" \
            --file deploy/docker/Dockerfile.back \
            --tag "aetheus-back:${SOURCE_COMMIT}" .
          docker build \
            --load \
            --file deploy/docker/Dockerfile.front \
            --tag "aetheus-front:${SOURCE_COMMIT}" .
          docker image inspect \
            "aetheus-back:${SOURCE_COMMIT}" \
            "aetheus-front:${SOURCE_COMMIT}" >/dev/null
          mkdir -p .pipeline-artifacts
          printf '%s' "${SOURCE_COMMIT}" > .pipeline-artifacts/source-commit

  - name: Deploy
    os: linux
    group: Deploy
    depends_on:
      - Build
    steps:
      - name: Bring the stack up
        checkout: true
        working_directory: "$(WORKSPACE)"
        timeout_seconds: 1800
        shell: |
          set -eu
          SOURCE_COMMIT="$(git rev-parse HEAD)"
          export AETHEUS_BACK_IMAGE="aetheus-back:${SOURCE_COMMIT}"
          export AETHEUS_FRONT_IMAGE="aetheus-front:${SOURCE_COMMIT}"
          export APP_VERSION="$(APP_VERSION)"
          cd "$(DEPLOY_DIR)"
          docker compose up -d
          # Refuse to call this a success until the database is actually reachable.
          for attempt in $(seq 1 30); do
            if curl -sf http://127.0.0.1:10026/health/ready; then exit 0; fi
            sleep 5
          done
          echo "The backend never became ready." >&2
          exit 1

  - name: Release
    os: linux
    group: Deploy
    depends_on:
      - Deploy
    steps:
      - name: Record the deployed release
        type: release
        version: "$(APP_VERSION)"
        changelog: true
        deployed: true
        timeout_seconds: 300

Reading that pipeline

  • checkout: true clones the pinned revision into $(WORKSPACE). Without it the stage runs on an empty directory.
  • depends_on is what serialises the three stages. Remove it and they would be dispatched in parallel.
  • The deploy step polls /health/ready rather than /health/live, because the process answers "live" before its database connection is usable. Polling the wrong endpoint is the classic way to declare a broken deployment successful.
  • The release step is a native type, not a script: the backend then knows the version, the commit and the artefacts, which is what makes a later rollback point at something precise.

Version numbers

1.0.$(BUILD_PIPELINE_RUNNUMBER) yields 1.0.1, 1.0.2 and so on, counting every launch of this pipeline including the failed ones. It is a build counter, not a semantic version.

First run

  1. Push the YAML to the default branch of the linked repository. The definition is read from Git at launch, so there is nothing to synchronise in the UI.

  2. Create the pipeline in the project, pointing at that slug, and attach the variable library and the vault.

  3. Launch it, and watch the run timeline. A stage stuck in a waiting state almost always means no eligible agent is online for its os.

  4. When it goes green, the run's overview carries the release it produced and the commit it was built from.

What this pipeline deliberately leaves out

The pipelines the project runs on itself add a great deal on top of this skeleton: immutable candidate artefacts, blue-green colour switching so the control plane never goes down while deploying itself, a rollback stage that runs only when the release fails, and retention. Add them when you need them; none of it is required to get an instance installed.

  • Pipelines - stages, steps, artefacts and templates in detail.
  • Apache - publish the two host names once the stack is up.