Reusable GitHub workflows that check, build and deploy Screenly Edge Apps:
- Checks: lint, format-check, build and test on pushes and pull requests.
- Initialize: create the Edge App and its first instance, once per environment.
- Update: deploy to stage on every push to the default branch, and to production on every
v*tag.
Bun apps with a build script are built and deployed from dist/; plain HTML apps are deployed as-is.
- Copy the examples into the app's
.github/workflows/:checks.yml(Bun apps only),initialize-edge-app.yml,update-edge-app.yml. - Create the GitHub environments
stageandproduction, and addSCREENLY_API_TOKENas an environment secret on both. Don't use a repository secret: it's readable from any branch and bypasses production approval. - Run Actions → Initialize Edge App for
stage. When it has finished successfully, set the repo variableSTAGE_EDGE_APP_IDto the id in its job summary. - Do the same for
production→PRODUCTION_EDGE_APP_ID. - Protect production.
Each example is a few lines that call this repo's workflows, so the logic lives here:
jobs:
update:
uses: Screenly/edge-apps-actions/.github/workflows/update.yml@v26.10.2
secrets:
SCREENLY_API_TOKEN: ${{ secrets.SCREENLY_API_TOKEN }}- Stage deploys on every push (or manual run) on the default branch (
main/master), after approval if thestageenvironment has required reviewers. Other branches never deploy. - Production deploys when you push a
v*tag on the default branch, after a reviewer approves. We recommend CalVervYY.M.PATCH:
git checkout main && git pull # or master
git tag v26.10.0 && git push origin v26.10.0
gh release create v26.10.0 --verify-tag --generate-notes --title v26.10.0To redo a failed or cancelled production deploy, re-run the latest tag's workflow run; you don't need a new tag. Don't cancel a deploy that's already running.
Check the live revision after a re-run. If the earlier run stopped after publishing but before promoting to
stable,screenly/cliv26.9.0 sees no file changes on the re-run, skips promotion and still reports success. Make sure the app is on the new revision in Screenly. If it isn't, push a new tag with a change to the app's files; that creates and promotes a new revision.
Pin an exact version and let Dependabot open the bump PRs; it also bumps reusable workflow refs. Add .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
target-branch: 'development' # optional: drop this line if the repo has no development branch
groups:
github-actions:
patterns: ['*']Don't pin @main or a moving tag: a bad release would reach every app's production at once.
The workflow checks only catch mistakes; whoever pushes a tag controls the workflow file at that commit. Enforce it with repo settings:
- Tag ruleset (Settings → Rules → Rulesets): target
v*, restrict creations, updates and deletions; bypass only the release team and admins. productionenvironment: required reviewers with Prevent self-review, and deployment branches/tags limited tov[0-9]*tags. Run production Initialize first; afterwards it can only start from av*tag.stageenvironment: deployment branches limited to the default branch.
- One deploy at a time per environment. A running deploy is never cancelled. A newer push or tag waits until the current run finishes or is rejected, and only the newest waiting run is kept. If an older run is still waiting for approval, reject it so the newest one goes next.
- Initialize has its own queue, so a deploy push can't replace a queued Initialize. That's also why the id variable must be set only after Initialize has finished: Update can't run without it.
- Production tag check. The tag's commit must be on the default branch.
- Plain HTML apps deploy the repo root;
.git/,.github/,.claude/andnode_modules/are always excluded. List anything else that isn't part of the app in.ignore. - Initialize safeguards. It refuses to run when the environment's id variable is already set, or when an app with the same name already exists (so a double-click can't create a duplicate). If it can't check, it stops without creating anything.
- Secrets. Only
SCREENLY_API_TOKENis passed. The workflows run in the app's repo, so its environments, reviewers and variables apply.
| Error | Fix |
|---|---|
edge_app_id is required |
Set STAGE_EDGE_APP_ID / PRODUCTION_EDGE_APP_ID (steps 3–4 of setup). |
screenly_api_token is empty / validation failed |
Add or fix SCREENLY_API_TOKEN on that environment. |
This environment is already initialized |
Nothing to do; use Update to deploy. |
An Edge App named … already exists |
If a previous Initialize created it and then failed, delete that app and re-run Initialize (below). If it's this repo's working app, set the id variable instead. |
Tag … is not on <default branch> |
Tag a commit that's on the default branch (main or master). |
Recovering from a failed Initialize: if Initialize created the app but a later step failed, the app has no instance. Don't set the id variable (Update never creates the instance). Delete the app and re-run Initialize:
# stage: also set API_BASE_URL=https://api.screenlyappstage.com
API_TOKEN=<token> EDGE_APP_ID=<id from the job summary> screenly edge-app deleteReusable workflows (call with uses: at the job level):
| Workflow | Inputs | Secrets |
|---|---|---|
checks.yml |
bun-version (default latest) |
none |
initialize.yml |
environment, edge_app_name, edge_app_title |
SCREENLY_API_TOKEN |
update.yml |
delete_missing_settings (default true) |
SCREENLY_API_TOKEN |
Actions (used by the workflows; can also be used as steps):
| Action | Inputs |
|---|---|
checks |
bun-version |
initialize |
screenly_api_token, edge_app_name, edge_app_title, environment (stage/production), edge_app_id |
update |
screenly_api_token, environment, edge_app_id (required), delete_missing_settings (default false) |
edge_app_name/edge_app_titlecan't contain",$,`,\or control characters.- When calling
initializedirectly, scope the id per environment:(inputs.environment == 'production' && vars.PRODUCTION_EDGE_APP_ID) || (inputs.environment == 'stage' && vars.STAGE_EDGE_APP_ID) || ''. - Requires
screenly/cliv26.9.0or later.
Releases are immutable CalVer tags (vYY.M.PATCH); the legacy @v1 tag is frozen. To release this repo, see CONTRIBUTING.md.