# Deploy with GitHub Actions


> Reusable workflows that check an edge app, create it in stage and production, and deploy every push, with each environment's app ID in a repository variable.

The [Edge Apps GitHub Actions](https://github.com/Screenly/edge-apps-actions) take an edge app from a repository to your screens. A push to the default branch deploys to stage, and a version tag deploys to production after a reviewer approves it.

They come in 2 forms. The reusable workflows hold the whole setup, so your repository's workflow files only pin a version and pass a token. The `checks`, `initialize`, and `update` actions underneath them are there to use as steps in a workflow of your own.

## The reusable workflows

Copy the 3 files in the repository's `examples/` folder into your app's `.github/workflows/`. Each one calls a workflow from the actions repository:

```yaml
name: Update Edge App

on:
  workflow_dispatch:
  push:
    branches: [main, master]
    tags: ['v[0-9]*']

permissions:
  contents: read

jobs:
  update:
    uses: Screenly/edge-apps-actions/.github/workflows/update.yml@v26.10.2
    secrets:
      SCREENLY_API_TOKEN: ${{ secrets.SCREENLY_API_TOKEN }}
```

When a later release changes how deploys work, bumping that pin is all it takes.

| 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` |

## Set up an app

1. Create the GitHub environments `stage` and `production`, and add `SCREENLY_API_TOKEN` to each as an environment secret. An environment secret only reaches jobs that run in that environment, so the production token stays behind production's reviewers.
2. Run "Initialize Edge App" for `stage`. Once the run has finished successfully, set the repository variable `STAGE_EDGE_APP_ID` to the ID in its job summary. Setting it earlier lets a deploy start while Initialize is still running.
3. Do the same for `production` and `PRODUCTION_EDGE_APP_ID`.

Both environments deploy from the same `screenly.yml`. The ID comes from the repository variable, not from the manifest, and it wins over any `id` in `screenly.yml`. A `screenly_qc.yml` is not read.

Use a token from an account with only the access the deploy needs. See [API tokens](/docs/developers/overview/api-tokens/).

## How deploys run

`update.yml` deploys to stage on a push or a manual run on the default branch. Other branches never deploy. It deploys to production when you push a `v*` tag whose commit is on the default branch, through the `production` environment and its reviewers.

Deploys to an environment run one at a time. While one runs, the next waits, and a newer push takes the waiting run's place. A running deploy is never canceled. Production tags queue separately from stage. If an older production run is still waiting for a reviewer, reject it so the newest one goes next.

`checks.yml` cancels a check still running on a branch when a newer push to that branch arrives.

`delete_missing_settings` is `true` in `update.yml`, so settings that exist in Screenly but not in your manifest are deleted on deploy. To keep them, pass `delete_missing_settings: false` under `with:`.

To redo a failed or canceled production deploy, re-run that tag's workflow run. If the earlier run stopped part-way, the re-run can report success while the app stays on its previous revision. Check the live revision in Screenly afterward, and if it didn't move, push a new tag with a change to the app's files.

## The actions on their own

### checks

Sets up Bun, installs dependencies with `bun install --frozen-lockfile`, and runs these `package.json` scripts in order, stopping at the first failure: `format:check`, `lint`, `build`, `test`. The repository needs all 4 scripts and a committed Bun lockfile.

```yaml
- uses: Screenly/edge-apps-actions/checks@v26.10.2
  with:
    bun-version: latest # optional
```

### initialize

Creates the edge app in an environment, deploys it, and creates its first instance. It is for the first deploy to an environment only.

```yaml
- uses: Screenly/edge-apps-actions/initialize@v26.10.2
  with:
    screenly_api_token: ${{ secrets.SCREENLY_API_TOKEN }}
    edge_app_name: my-edge-app
    edge_app_title: My Edge App
    environment: stage
    edge_app_id: ${{ vars.STAGE_EDGE_APP_ID }}
```

`environment` is `stage` or `production`. If `edge_app_id` is set, the run stops and points you to `update`. It also stops if an edge app with the same name already exists in that environment, so running it twice can't create 2 apps.

The new app's ID appears as soon as the app exists, before the build and deploy steps: as a notice in the log, and under "Edge App id" in the job summary, with the name of the variable to set.

To start an environment over, delete its edge app in Screenly, clear the ID variable, and run `initialize` again.

### update

Builds and deploys a new version of an existing app.

```yaml
- uses: Screenly/edge-apps-actions/update@v26.10.2
  with:
    screenly_api_token: ${{ secrets.SCREENLY_API_TOKEN }}
    environment: production
    edge_app_id: ${{ vars.PRODUCTION_EDGE_APP_ID }}
    delete_missing_settings: true # optional
```

`edge_app_id` is required. If it's empty, or holds a leftover like `false`, `null`, or `0` from an expression that didn't resolve, the run stops in its first step and names the variable to set. `delete_missing_settings` is `false` by default on the action.

### What both deploying actions do

`initialize` and `update` stop before doing anything else when `screenly_api_token` is empty, when `environment` is not `stage` or `production`, or when Screenly rejects the token.

An app whose `package.json` has a `build` script is built and deployed from `dist/`. An app without one is deployed from the repository root as it is. If `package.json` can't be parsed, the run stops with an error.

Each release of the actions runs a fixed version of the CLI, so a new CLI release doesn't change how your app deploys until you move to a newer release of the actions.

## Versions

Releases are numbered by year and month, such as `v26.10.2`, and each is 1 fixed tag. There is no moving tag that follows the latest release. `v1` stays on 1.3.0 for good and gets nothing newer, so pin an exact tag and bump it when you want a newer release. Dependabot's `github-actions` updates can open those bumps for you.