Skip to content

appaloft/deploy-action

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Appaloft Deploy Action

Run Appaloft deployments from GitHub Actions.

The default mode is a thin wrapper around the released appaloft binary for pure SSH deployments. Self-hosted server API mode is available for repositories that already have their project, environment, resource, and deployment target registered in an Appaloft server. In self-hosted mode, the action may read non-secret project/environment/resource/server ids from controlPlane.deploymentContext in committed appaloft.yml; credentials, tokens, and secret values still come from trusted workflow inputs or secrets.

Basic Deploy

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4

      - uses: appaloft/deploy-action@v1
        with:
          version: v0.9.0
          config: appaloft.yml
          ssh-host: ${{ secrets.APPALOFT_SSH_HOST }}
          ssh-user: ${{ secrets.APPALOFT_SSH_USER }}
          ssh-private-key: ${{ secrets.APPALOFT_SSH_PRIVATE_KEY }}

Pin version to an Appaloft CLI release for production workflows. version: latest is useful for quick experiments, but it trades repeatability for convenience.

Minimal appaloft.yml:

runtime:
  strategy: workspace-commands
  buildCommand: bun install && bun run build
  startCommand: bun run start
network:
  internalPort: 3000

Application secrets should be mapped by the workflow and referenced from config, not committed as values:

secrets:
  DATABASE_URL:
    from: ci-env:DATABASE_URL

Install Self-Hosted Console

Use command: install-console when a workflow should install or upgrade an Appaloft console on an SSH host before other repositories deploy through control-plane-mode: self-hosted.

name: Install Appaloft Console

on:
  workflow_dispatch:

jobs:
  install:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    environment:
      name: appaloft-console
      url: ${{ steps.console.outputs.console-url }}
    steps:
      - uses: appaloft/deploy-action@v1
        id: console
        with:
          command: install-console
          version: latest
          ssh-host: ${{ secrets.APPALOFT_CONSOLE_SSH_HOST }}
          ssh-user: ${{ secrets.APPALOFT_CONSOLE_SSH_USER }}
          ssh-private-key: ${{ secrets.APPALOFT_CONSOLE_SSH_PRIVATE_KEY }}
          console-domain: console.example.com
          console-database: postgres
          console-proxy: traefik
          console-orchestrator: compose
          console-skip-docker-install: true

The action connects to the SSH host, downloads the matching Appaloft release install.sh, runs the self-hosted Docker installer with the selected public console origin and Docker orchestrator, and verifies /api/health. The installer defaults to PostgreSQL, direct host access on port 3721, and an Appaloft-managed Traefik edge proxy. console-domain is passed to the installer as the Appaloft instance console route. console-url may be supplied directly when the public origin is not https://<console-domain>. Set console-orchestrator: swarm to deploy the console as a Docker Swarm stack; console-swarm-init: true may initialize a single-node Swarm manager when the host is not already a manager. Use console-proxy: none only when another reverse proxy owns public routing. This command is separate from deploy, so the original pure SSH CLI deployment path remains available.

Non-secret console install settings can live in the selected config file. SSH host, SSH key, API tokens, and raw database credentials still come from trusted workflow inputs or secrets.

controlPlane:
  mode: self-hosted
  url: https://console.example.com
  deploymentContext:
    projectId: prj_www
    environmentId: env_prod
    resourceId: res_www
    serverId: srv_prod
    # destinationId is optional.
  install:
    database: postgres
    domain: console.example.com
    proxy: traefik
    orchestrator: swarm
    httpPort: 3721
    swarmStackName: appaloft-console
    swarmInit: true

When command: install-console is used, explicit action inputs override the matching controlPlane.install.* values. If console-url is omitted, the action uses controlPlane.install.url, then controlPlane.url, then https://<console-domain>, and finally http://<ssh-host>:<console-http-port>.

Pull Request Preview

Action-only pull request previews require a workflow file. The action does not install a webhook or make GitHub run previews on its own.

name: Appaloft Preview

on:
  pull_request:
    types: [opened, reopened, synchronize]

jobs:
  preview:
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    environment:
      name: preview-pr-${{ github.event.pull_request.number }}
      url: ${{ steps.deploy.outputs.preview-url }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - uses: appaloft/deploy-action@v1
        id: deploy
        with:
          version: v0.9.0
          config: appaloft.preview.yml
          preview: pull-request
          preview-id: pr-${{ github.event.pull_request.number }}
          preview-domain-template: pr-${{ github.event.pull_request.number }}.preview.example.com
          preview-tls-mode: disabled
          require-preview-url: true
          pr-comment: true
          github-token: ${{ github.token }}
          ssh-host: ${{ secrets.APPALOFT_SSH_HOST }}
          ssh-user: ${{ secrets.APPALOFT_SSH_USER }}
          ssh-private-key: ${{ secrets.APPALOFT_SSH_PRIVATE_KEY }}
          environment-variables: |
            HOST=0.0.0.0
            PORT=3000
          secret-variables: |
            DATABASE_URL=ci-env:DATABASE_URL

The default example skips fork pull requests before deployment credentials are exposed. Fork previews need an explicit reduced-credential policy.

Use appaloft.preview.yml when the root config is production-oriented. Preview route intent should come from generated/default access, this trusted preview-domain-template, or an explicitly selected preview config file. Production access.domains[] should not be reinterpreted as pull request preview hostnames.

Preview Cleanup

Add a separate close-event workflow so preview runtime and route state are cleaned when the pull request closes:

name: Appaloft Preview Cleanup

on:
  pull_request:
    types: [closed]

jobs:
  cleanup:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4

      - uses: appaloft/deploy-action@v1
        with:
          command: preview-cleanup
          version: v0.9.0
          config: appaloft.preview.yml
          preview: pull-request
          preview-id: pr-${{ github.event.pull_request.number }}
          pr-comment: true
          github-token: ${{ github.token }}
          ssh-host: ${{ secrets.APPALOFT_SSH_HOST }}
          ssh-user: ${{ secrets.APPALOFT_SSH_USER }}
          ssh-private-key: ${{ secrets.APPALOFT_SSH_PRIVATE_KEY }}

Cleanup is idempotent. It stops preview-owned runtime state when present, removes preview route desired state, unlinks preview source identity, and preserves production deployments and ordinary deployment history.

Self-Hosted Server API Mode

Use this mode when a self-hosted Appaloft server owns deployment state and the repository should only trigger a deployment through the server API. The resource profile must already exist in the server; this first slice does not apply appaloft.yml, upload a source archive, create resources, or apply preview route/profile inputs from the runner.

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    environment:
      name: production
      url: ${{ steps.deploy.outputs.console-url }}
    steps:
      - uses: appaloft/deploy-action@v1
        id: deploy
        with:
          control-plane-mode: self-hosted
          control-plane-url: https://console.example.com
          appaloft-token: ${{ secrets.APPALOFT_TOKEN }}
          project-id: ${{ secrets.APPALOFT_PROJECT_ID }}
          environment-id: ${{ secrets.APPALOFT_ENVIRONMENT_ID }}
          resource-id: ${{ secrets.APPALOFT_RESOURCE_ID }}
          server-id: ${{ secrets.APPALOFT_SERVER_ID }}

Server API mode performs a lightweight compatibility check against /api/version, derives a safe source fingerprint from GitHub repository context and config path, and calls POST /api/action/deployments/from-source-link. When trusted ids are supplied, the server can use them to bootstrap a missing source link before later runs omit ids. When ids are omitted, the server resolves project, environment, resource, and target from existing source-link state. It does not install or invoke the Appaloft CLI, open SSH, or read or write SSH-server PGlite state.

server-config-deploy: true selects the next self-hosted server config workflow. In that mode the action feature-gates server support through /api/version before source package handoff and then calls POST /api/action/deployments/from-config-package. A server that does not advertise source package and server-side config bootstrap support fails before package upload or state mutation. This mode is for compatible 0.9.x self-hosted servers; leave it unset for the existing source-link trigger mode.

- uses: appaloft/deploy-action@v1
  id: deploy
  with:
    server-config-deploy: true
    config: appaloft.yml

With this shape, controlPlane.mode, controlPlane.url, and controlPlane.deploymentContext come from appaloft.yml. Explicit action inputs still override the matching config values when a workflow needs to do so. appaloft-token is optional in the current 0.9.x slice, but production hardening will require a deploy token or OIDC exchange for Action mutation endpoints.

If the committed config declares secrets.KEY.from: ci-env:NAME, expose the GitHub secret to the runner environment and list the matching secret-variables entry. The action resolves the value locally, sends it only as transient API payload to the self-hosted server, and the server applies it as a runtime secret through its environment-variable operation.

- uses: appaloft/deploy-action@v1
  id: deploy
  env:
    APPALOFT_BETTER_AUTH_SECRET: ${{ secrets.APPALOFT_BETTER_AUTH_SECRET }}
  with:
    control-plane-mode: self-hosted
    control-plane-url: https://console.example.com
    appaloft-token: ${{ secrets.APPALOFT_TOKEN }}
    server-config-deploy: true
    config: appaloft.yml
    secret-variables: |
      APPALOFT_BETTER_AUTH_SECRET=ci-env:APPALOFT_BETTER_AUTH_SECRET

For preview: pull-request, server API mode derives a preview-scoped source fingerprint and calls the same deployment endpoint. It writes preview-id, deployment-id, deployment-url, and console-url outputs. Existing source-link server mode does not apply runner-side route/profile inputs. In server-config-deploy mode, pull request previews may keep using environment-variables, preview-domain-template, preview-tls-mode, and require-preview-url for per-run values such as PR ports, callback URLs, and preview hostnames. Those values are sent as transient API payload; they do not need to be committed to appaloft.yml. Production config domains from appaloft.yml are not reused for pull request preview routing unless a later preview-safe policy explicitly selects them. runtime-name remains a pure SSH CLI input and is not accepted by server config deploy. In server-config-deploy mode, secret-variables is reserved for resolving committed ci-env: secret references before the API request.

- uses: appaloft/deploy-action@v1
  id: deploy
  with:
    control-plane-mode: self-hosted
    control-plane-url: https://console.example.com
    appaloft-token: ${{ secrets.APPALOFT_TOKEN }}
    server-config-deploy: true
    config: appaloft.preview.yml
    preview: pull-request
    preview-id: pr-${{ github.event.pull_request.number }}
    preview-domain-template: pr-${{ github.event.pull_request.number }}.preview.example.com
    preview-tls-mode: disabled
    require-preview-url: true
    environment-variables: |
      HOST=0.0.0.0
      PORT=4321
      APPALOFT_BETTER_AUTH_URL=http://pr-${{ github.event.pull_request.number }}.preview.example.com

For command: preview-cleanup, server API mode derives the preview-scoped source fingerprint from the trusted preview and preview-id inputs and calls POST /api/deployments/cleanup-preview. Cleanup context is resolved from source-link state; project/resource/server ids are not accepted for server-mode preview cleanup.

Server API mode writes the console URL and deployment detail URL to the GitHub step summary when GitHub provides GITHUB_STEP_SUMMARY. When the server response includes a deployment href or URL, the action uses that server-provided console target; otherwise it falls back to the standard /deployments/{deploymentId} console route. For cleanup it writes the console URL and cleanup status. Workflows can also use the console-url or deployment-url output for environment URLs or PR comments.

When pr-comment: true, the action posts or updates one stable pull request comment with the preview URL, console URL, deployment detail URL, or cleanup status that is available for the selected mode. The workflow must pass github-token: ${{ github.token }} and grant pull-requests: write or issues: write. This is entrypoint feedback only; product-grade GitHub App comments/checks remain control-plane features. Comment publishing is best-effort: GitHub API permission failures are reported as warnings and do not fail an otherwise successful deployment or cleanup.

The control-plane connection policy can live in appaloft.yml:

controlPlane:
  mode: self-hosted
  url: https://console.example.com

Explicit action inputs override config values. Project, environment, resource, server, token, SSH, and database identity still come from trusted workflow inputs, variables, secrets, existing source-link state, or the Appaloft server, not from committed config.

Inputs

Input Default Purpose
command deploy deploy, preview-cleanup, or install-console.
version latest Appaloft release tag such as v0.9.0. Used for CLI install and self-hosted console install.
config empty Optional Appaloft config path. If omitted, appaloft.yml is used only when present.
source . Source path or locator passed to the CLI.
runtime-name empty Trusted runtime name override for deploy.
ssh-host empty SSH target host for pure SSH deployments.
ssh-user empty SSH username.
ssh-port empty SSH port.
ssh-private-key empty SSH private key value, written to a temp file before invoking Appaloft.
ssh-private-key-file empty Existing runner-local private key path. Mutually exclusive with ssh-private-key.
console-url empty Public console origin for command: install-console. Defaults to https://<console-domain> or http://<ssh-host>:<console-http-port>.
console-domain empty Public console domain used to derive console-url when console-url is empty.
console-database config or postgres Self-hosted console database backend for command: install-console; pglite or postgres.
console-orchestrator config or compose Self-hosted Docker orchestrator for command: install-console; compose or swarm.
console-proxy config or traefik Self-hosted console proxy mode for command: install-console; traefik or none.
console-http-host config or 0.0.0.0 Host bind address passed to the self-hosted console installer.
console-http-port config or 3721 Host HTTP port passed to the self-hosted console installer.
console-install-dir empty Remote install directory passed to the self-hosted console installer. Empty uses the installer default.
console-compose-project-name config or appaloft Docker Compose project name passed to the self-hosted console installer.
console-swarm-stack-name config or appaloft Docker Swarm stack name passed to the self-hosted console installer.
console-swarm-init config or false Initialize a single-node Swarm manager when console-orchestrator is swarm.
console-swarm-advertise-addr empty Optional advertise address passed to docker swarm init.
console-image config or ghcr.io/appaloft/appaloft Appaloft console image repository or full image reference passed to the self-hosted console installer.
console-installer-url empty Override URL for the self-hosted install.sh used by command: install-console.
console-skip-docker-install false Require Docker Engine to already exist on the SSH host during command: install-console.
server-provider generic-ssh Server provider key.
server-proxy-kind empty Server proxy kind such as traefik or caddy.
state-backend empty Explicit state backend. SSH targets default to ssh-pglite.
environment-variables empty Newline-separated values passed as repeated CLI --env flags in pure SSH CLI mode. In server-config-deploy pull request previews, sent as transient server API payload and applied through the server environment-variable operation after committed env values, so Action values can override per-preview fields.
secret-variables empty Newline-separated values passed as repeated CLI --secret flags in pure SSH CLI mode. In server-config-deploy mode, resolves ci-env: references and sends the matched values as transient server API payload for committed config secrets entries. Prefer ci-env: references over raw secret values.
preview empty Use pull-request for PR preview deploy or cleanup.
preview-id empty Trusted preview scope, for example pr-123. Required for pull request previews.
preview-domain-template empty Trusted preview hostname for deploy, for example pr-123.preview.example.com.
preview-tls-mode empty Preview TLS mode for preview-domain-template.
require-preview-url false Fail deploy if no public preview URL can be resolved.
pr-comment false Post or update one pull request comment with preview, deployment, cleanup, and console feedback.
github-token empty GitHub token used only when pr-comment is true.
control-plane-mode empty Use none for pure SSH CLI mode or self-hosted for server API mode. When empty, controlPlane.mode from config may select the mode; otherwise the effective default is none.
control-plane-url empty Self-hosted Appaloft server endpoint for server API mode. When empty, controlPlane.url from config may supply the endpoint.
appaloft-token empty Optional bearer token for server API mode.
use-oidc false Reserved for future GitHub OIDC exchange.
server-config-deploy false Experimental self-hosted mode that calls POST /api/action/deployments/from-config-package after the server advertises source package and server-side config bootstrap support.
project-id config or empty Optional trusted project id for server API mode. Defaults to controlPlane.deploymentContext.projectId when present.
environment-id config or empty Optional trusted environment id for server API mode. Defaults to controlPlane.deploymentContext.environmentId when present. Required only when any explicit deployment id is supplied.
resource-id config or empty Optional trusted resource id for server API mode. Defaults to controlPlane.deploymentContext.resourceId when present. Required only when any explicit deployment id is supplied.
server-id config or empty Optional trusted deployment target id for server API mode. Defaults to controlPlane.deploymentContext.serverId when present. Required only when any explicit deployment id is supplied.
destination-id config or empty Optional trusted destination id for server API mode. Defaults to controlPlane.deploymentContext.destinationId when present.

Outputs

Output Purpose
appaloft-version Installed CLI version.
appaloft-target Selected release target.
preview-id Preview id when preview mode is selected.
preview-url Public preview URL when Appaloft resolves one during deploy.
deployment-id Deployment id accepted by Appaloft.
deployment-url Self-hosted Appaloft console deployment detail URL when available.
console-url Self-hosted Appaloft console URL installed by install-console or used by server API mode.
preview-cleanup-status Cleanup status returned by server API mode for command: preview-cleanup.

Security Notes

  • ssh-private-key is written to a runner temp file with mode 0600; raw key material is not passed as a command-line argument.
  • Do not commit SSH keys, tokens, database URLs, production secret values, or Appaloft identity selectors into appaloft.yml.
  • The action defaults SSH deployments to server-owned ssh-pglite state when ssh-host is set and no control plane is selected.
  • control-plane-mode: self-hosted does not accept SSH keys or state-backend; the action calls the Appaloft server API and leaves state ownership with the server.
  • In self-hosted server API mode, preview deploy accepts trusted preview and preview-id inputs for source fingerprinting and feedback outputs. Preview route/profile inputs remain rejected by source-link trigger mode. server-config-deploy may accept preview route and environment inputs only with preview=pull-request; command: preview-cleanup accepts only source/config and trusted preview scope inputs. Deployment target ids are intentionally ignored/rejected for cleanup because cleanup resolves from server-owned source-link state.
  • server-config-deploy requires explicit self-hosted server support. The action fails before source package handoff when the server handshake does not advertise the required capability.
  • In server-config-deploy mode, secret-variables supports only KEY=ci-env:NAME references. Missing runner environment values fail before the server API request, and raw secret values are not written to step summaries or PR comments.
  • pr-comment requires explicit workflow permission and token wiring. The action updates the same marker comment for the PR instead of creating a new comment on each run. Comment API failures are warnings so they do not mask a successful deployment.

Product-Grade Previews

This action supports workflow-file previews. Product-grade previews with GitHub App webhooks, preview policy, comments/checks, cleanup retries, quotas, audit, and managed domain lifecycle are future Appaloft Cloud or self-hosted control-plane features.

About

Deploy with Appaloft from GitHub Actions

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages