Skip to content

The GitHub Action

The sealup action runs the same code as the CLI, in the same process, from the bundle in the release: nothing is downloaded when the job runs. Add it after your build step:

.github/workflows/deploy.yml
- uses: sealup/sealup@v0.2.1
id: sealup
env:
SEALUP_PASSWORD: ${{ secrets.SEALUP_PASSWORD }}

sealup init writes a whole workflow around this step (see Getting started): on every push to main, it checks out the code, builds the site, and deploys it.

The step above names a release by its full version. sealup’s GitHub releases are immutable: once a release is out, its tag cannot be moved or deleted while the release exists, and its name can never be used again. So @v0.x.y runs the code released as v0.x.y, or nothing at all. sealup init writes the step for the version you run, each release’s notes on GitHub give the line for that release, and Dependabot updates it like any other action.

The step needs that version. Without an @, GitHub refuses the step. @main runs whatever is on main that day: code not yet released, which can change between two runs. sealup has no moving tag such as @v0.

The other steps of init’s workflow, actions/checkout and actions/setup-node, stay pinned to a full commit SHA with their version in a comment: nothing locks their tags.

If your policy asks for every action pinned to a commit (zizmor, or OpenSSF Scorecard’s pinned dependencies check), use the release’s commit, shown on its release page, with the version in a comment. It runs the same code as the tag:

- uses: sealup/sealup@<commit SHA> # v0.x.y

Secrets go to the step through env:, never through with:: sealup.yml reads them as ${NAME}, as on your computer. Add each one under Settings › Secrets and variables › Actions, or in a GitHub environment to keep production’s apart. Before its first line of output, sealup asks GitHub to mask each secret (each line of a key), and it never prints them.

In a public repository, the server and the host key can be secrets too, and are masked in the log like one: sealup init offers it (see A public repository). That hides them from strangers. It does not stop someone who can push from pointing sealup.yml at another server in the same change: keep the password in an environment that only main may use, and require review on main.

Input Default What it does
target The target to deploy. Needed when sealup.yml has several: CI never shows a picker
config Path to sealup.yml. Empty: searched from working-directory up to the repo root
dry-run false Show every change, change nothing. Writes the plan as JSON and sets status to dry-run
working-directory . The folder to run in: where the search for sealup.yml starts
Output What it holds
status deployed, unchanged, dry-run, partial, failed, blocked (a safety net, exit 6) or cancelled. The step fails on the last four
url The target’s url from sealup.yml: use it for environment.url
uploaded Files uploaded (or that would be, on a dry run)
deleted Files deleted (or that would be, on a dry run)
unchanged Files already up to date
bytes Bytes uploaded
duration The whole run, in seconds
plan The path to the plan’s JSON, on a dry run only

Name the target with with:. A staging deploy on every push and production on a tag, for example, are two jobs, each with its own environment and secrets:

- uses: sealup/sealup@v0.x.y
id: sealup
with:
target: staging
env:
STAGING_PASSWORD: ${{ secrets.STAGING_PASSWORD }}

With dry-run: true, the step plans and changes nothing; its job summary lists every change. Run it on pull requests to review a deploy before it happens. A dry run still logs in to compare with the server, so it needs the secrets too, and GitHub gives none to pull requests from forks.

init’s workflow sets a concurrency group: a newer push waits for the running deploy, which is never cancelled half-way. sealup’s own lock covers deploys GitHub cannot see (a laptop, another workflow): see One deploy at a time.

The job’s log shows the same lines as a terminal, with the list of changes in a collapsed group. The job summary gets a table of the target, the counts, the timing, and the changes. A stop is an error annotation with its reason and a link to its page here; plain FTP and a dropped connection get their own annotation.

The action is a convenience: in any CI, npx sealup@<version> deploy does the same, with the variables in the job’s environment.