Overview of GitHub Actions CI#
Squareone uses GitHub Actions to drive both continuous integration (CI) and continuous delivery (CD) workflows. This page gives an overview of the CI workflows and how they work together.
For an overview of the tooling that GitHub Actions runs for testing and releases, see Overview of the monorepo architecture.
Workflow architecture overview#
Squareone uses a combination of standalone and reusable workflows:
ci.yaml
├─→ build-squareone.yaml (reusable)
├─→ Builds Docker images for branches/PRs
└─→ Triggers dependabot-auto-merge.yaml (on completion)
release.yaml (on push to main)
└─→ Creates GitHub Releases
└─→ Triggers docker-release.yaml
└─→ build-squareone.yaml (reusable)
dependabot-changesets.yaml
└─→ Auto-generates changesets for Dependabot PRs
dependabot-auto-merge.yaml (triggered by ci.yaml)
└─→ Enables auto-merge for Dependabot PRs after CI passes
codeql-analysis.yaml
└─→ Security scanning (weekly + on main)
Currently disabled workflows
chromatic-squareone.yaml (disabled)
chromatic-squared.yaml (disabled)
├─→ run-chromatic.yaml (reusable)
└─→ Visual regression testing infrastructure (available for future use)
See individual sections below for detailed documentation of each workflow.
Testing branches and pull requests — ci.yaml#
The ci.yaml workflow tests each pull request and pushes to main.
This CI workflow builds each package and application, and runs tests and linters with Turborepo pipelines.
Triggers:
push: All branches exceptdependabot/**,renovate/**,tickets/**,u/**pull_request: All pull requests
Workflow jobs#
The ci.yaml workflow consists of four main jobs:
Path filtering (changes job)#
Uses the dorny/paths-filter action to detect if the docs/ directory has changed.
This allows the documentation build job to run conditionally, saving CI time when docs haven’t been modified.
Main testing (test job)#
Runs comprehensive testing in a Playwright container (mcr.microsoft.com/playwright).
The container ensures consistent browser testing environments.
Key steps:
Docker Version Validation: Runs
scripts/validate-docker-versions.jsto ensure Dockerfile version tags matchpackage.jsonversions for Node.js, pnpm, and TurborepoFormat Check: Verifies code formatting with Prettier
Lint: Runs ESLint with Turborepo remote caching
Build: Builds all packages and applications with Turborepo remote caching
Test: Runs vitest tests and Storybook tests with Turborepo remote caching
The workflow uses Turborepo’s remote caching (configured via TURBO_TOKEN, TURBO_API, TURBO_TEAM) to speed up builds by reusing cached results from previous runs.
Docker image build (build-squareone job)#
Builds Docker images for the squareone application using the .github/workflows/build-squareone.yaml reusable workflow.
Conditional execution:
Runs on: Git tags,
tickets/**branches,dependabot/**PRs,changeset-release/**branchesPush behavior: Pushes images to
ghcr.ioregistry except for:dependabot/**branches (build-only validation)changeset-release/**branches (build-only validation)
For branches that push images, they are tagged with the branch name.
For example, the tickets/DM-40262 branch will build and push the ghcr.io/lsst-sqre/squareone:tickets-DM-40262 image.
You can use these images to test your application in Phalanx development environments without making a full release.
Note
Each application has its own build job in ci.yaml.
Remember to add a new job when you add an application to Squareone.
Documentation build and deploy (docs job)#
Runs conditionally only if the docs/ directory has changed (based on the changes job output).
Builds the Sphinx-based documentation site and deploys it to https://squareone.lsst.io using the lsst-sqre/ltd-upload action.
The default branch is deployed to https://squareone.lsst.io/ and other branches are deployed to https://squareone.lsst.io/v/.
Preparing and making releases — release.yaml#
The release machine is driven by the release.yaml workflow, which runs the Changesets action, changesets/action.
This workflow is triggered on pushes to main and operates in two modes: preparing releases and making releases.
Concurrency control: The workflow uses a concurrency group (per workflow and ref) to prevent concurrent releases, ensuring only one release process runs at a time.
Workflow configuration#
The release.yaml workflow includes several important configuration steps:
GitHub App authentication#
Uses the tibdex/github-app-token action to generate a GitHub token from the Squareone CI GitHub App credentials (SQUAREONE_CI_GH_APP_ID and SQUAREONE_CI_GH_APP_PRIVATE_KEY secrets).
This token is used instead of the default GITHUB_TOKEN because commits made with the default token don’t trigger subsequent GitHub Actions workflows.
GitHub Packages authentication#
Creates a ~/.npmrc file to authenticate with GitHub Packages using the GitHub App token.
This allows the workflow to publish npm packages to the GitHub Packages registry.
Git identity configuration#
Manually configures Git identity for the GitHub App bot user:
User name:
squareone-ci[bot]Email:
<app-id>+squareone-ci[bot]@users.noreply.github.com
The changesets action is configured with setupGitUser: false to use this manually configured identity instead of the action’s default.
This configuration ensures that commits made by the changesets action are properly attributed to the GitHub App bot user so that they can trigger GitHub Actions workflows.
Preparing releases#
First, when the changesets action detects a merge with changeset fragments in the .changeset/ directory, it creates or updates the changeset-release and associated Version packages pull request.
In this pull request, the changesets action runs the changeset version command (via the ci:version script in package.json) to update the version numbers in package.json of all packages that have changed.
Changesets also deletes the individual changeset fragments in .changeset/ and updates the changelogs of the affected packages.
Making releases#
When a maintainer merges the changeset-release pull request, the changesets action runs again from the main branch.
This time, it creates the package releases for each package and application that has changed.
For each package/application, changesets creates a GitHub Release containing the changelog update and tags the main branch with the new version number.
For example, if the Squareone application is updated, the tag is squareone@1.2.3.
If the rubin-style-dictionary package is updated, the tag is @lsst-sqre/rubin-style-dictionary@1.2.3.
For public npm packages, changesets publishes the new version to the npm registry. Squareone uses GitHub Packages for its npm registry.
This workflow does not handle Docker images; see docker-release.yaml for that.
However, release.yaml does effectively trigger the Docker release workflows by creating a new GitHub release.
Note
The changesets action in release.yaml uses a GitHub App, Squareone CI to issue a GitHub token rather than the default GitHub Actions token, GITHUB_TOKEN.
This is because Git commits made via that default token don’t trigger further GitHub Actions workflows.
Releasing Docker images — docker-release.yaml#
The docker-release.yaml workflow is triggered by a new GitHub release being published.
Effectively, docker-release.yaml is triggered automatically by the release.yaml workflow when it runs the second phase of making a release.
Workflow structure#
The workflow uses a two-job structure:
get-squareone-version: Extracts the version number from
apps/squareone/package.jsonrelease-squareone: Calls the
.github/workflows/build-squareone.yamlreusable workflow to build and push the Docker image with the extracted version as the tag
This workflow currently only handles the squareone application. If additional applications are added to the monorepo, new jobs should be added following the same pattern.
The Docker images are tagged according to their package.json version number.
For example, if the Squareone application is updated to version 1.2.3, the full image name is ghcr.io/lsst-sqre/squareone:1.2.3.
Automating changesets for Dependabot — dependabot-changesets.yaml#
The dependabot-changesets.yaml workflow automatically creates changeset files for dependency updates made by Dependabot.
This ensures that Dependabot PRs follow the same versioning and changelog process as manual changes.
The workflow is triggered by pull_request events (opened or synchronized) and runs only for PRs created by dependabot[bot].
It uses the StafflinePeoplePlus/dependabot-changesets action to generate appropriate changeset markdown files based on the dependency changes.
Security considerations#
This workflow implements several security best practices:
Uses ``pull_request`` trigger (not
pull_request_target) to avoid “pwn request” vulnerabilities where untrusted code could execute with write permissionsVerifies PR author using
github.event.pull_request.user.logininstead ofgithub.actorto prevent “Confused Deputy” attacksFollows GitHub’s recommendations for safely automating Dependabot workflows
For more details on the security design, see the comments in the workflow file.
Dependabot secrets requirement#
Important
This workflow requires GitHub App credentials to be configured as Dependabot secrets, not just GitHub Actions secrets.
When Dependabot triggers a workflow via pull_request events, GitHub treats it as if it came from a repository fork for security reasons.
This means:
Only Dependabot secrets are accessible to the workflow (GitHub Actions secrets are not available)
The default
GITHUB_TOKENhas read-only permissionsThis is by design to prevent secret leakage through malicious dependency updates
The workflow uses a GitHub App (Squareone CI) to generate a token that can trigger other workflows (commits made with the default GITHUB_TOKEN don’t trigger workflows).
The app credentials must be available as Dependabot secrets.
Configuring Dependabot secrets#
Organization administrators must configure these secrets at the organization or repository level:
Navigate to Settings → Security → Secrets and variables → Dependabot
Create two Dependabot secrets with the same values as the corresponding GitHub Actions secrets:
SQUAREONE_CI_GH_APP_ID(numeric app ID)SQUAREONE_CI_GH_APP_PRIVATE_KEY(PEM-formatted private key)
Set repository access to include
squareone
Note
These secrets must be duplicated (exist as both Actions secrets and Dependabot secrets) because GitHub isolates Dependabot workflows for security. If the credentials are rotated, both sets of secrets must be updated.
Without these Dependabot secrets configured, the workflow will fail with an error like “app_id is not set” even though the GitHub Actions secrets are properly configured.
For more information, see GitHub’s documentation on Automating Dependabot with GitHub Actions and Managing encrypted secrets for Dependabot.
Auto-merging Dependabot PRs — dependabot-auto-merge.yaml#
The dependabot-auto-merge.yaml workflow automatically enables auto-merge for Dependabot pull requests that meet specific criteria, streamlining the dependency update process while maintaining safety and quality standards.
This workflow works in conjunction with dependabot-changesets.yaml to provide a complete automated dependency update pipeline: changesets are automatically created, CI runs, and then auto-merge is enabled for qualifying PRs.
Triggers:
workflow_run: Triggered when theci.yamlworkflow completes successfullyOnly processes PRs (not direct pushes)
Workflow behavior#
The workflow follows these steps to safely enable auto-merge:
Extract PR information from the workflow run event
Verify PR author is
dependabot[bot]using secure identity checkingFetch Dependabot metadata to determine the semantic version update type
Check for changeset file to ensure the PR has versioning information
Enable auto-merge with squash strategy if all conditions are met
Comment on PR for visibility and audit trail
Auto-merge conditions#
Auto-merge is enabled only when ALL of the following conditions are met:
✅ PR is authored by
dependabot[bot]✅ CI workflow completed successfully
✅ Update type is NOT a major version update (
version-update:semver-major)✅ Changeset file exists in the PR (created by
dependabot-changesets.yaml)
Excluded updates:
❌ Major version updates (e.g.,
v1.x.x→v2.0.0) - These require manual review due to potential breaking changes❌ PRs without changesets - The workflow waits for
dependabot-changesets.yamlto complete first
Merge strategy#
All auto-merged Dependabot PRs use the squash merge strategy, which:
Combines all commits from the PR into a single commit on the main branch
Maintains a clean git history
Preserves the Dependabot PR title and changeset information
The PR will merge automatically once all branch protection requirements are satisfied (e.g., required status checks, required reviews if configured).
Security considerations#
This workflow implements several security best practices aligned with the dependabot-changesets.yaml workflow:
Secure trigger pattern:
Uses
workflow_runtrigger which executes in the context of the default branch, not the PR branchAvoids “pwn request” vulnerabilities where untrusted code could execute with elevated permissions
Only processes workflow runs triggered by
pull_requestevents, not direct pushes
Safe identity verification:
Verifies PR author using
github.event.workflow_run.pull_requests[0].user.logininstead ofgithub.actorPrevents “Confused Deputy” attacks where an attacker could impersonate Dependabot
Follows GitHub’s recommendations for safely automating Dependabot workflows
Minimal permissions:
contents: read- Only reads repository datapull-requests: write- Required to enable auto-merge and add commentsNo additional permissions beyond what’s necessary
Authentication:
Uses default
GITHUB_TOKENwhich is automatically scoped to the repositoryNo additional secrets required (unlike workflows that need to trigger other workflows)
Timing and workflow coordination#
The dependabot-auto-merge.yaml workflow is designed to run after both the dependabot-changesets.yaml and ci.yaml workflows have completed:
1. Dependabot opens PR
↓
2. dependabot-changesets.yaml runs (pull_request trigger)
├─→ Creates changeset file
└─→ Commits to PR
↓
3. ci.yaml runs (pull_request trigger, includes new changeset)
├─→ Runs tests
├─→ Runs linting
├─→ Builds packages
└─→ Completes successfully
↓
4. dependabot-auto-merge.yaml runs (workflow_run trigger)
├─→ Verifies changeset exists
├─→ Checks update type
└─→ Enables auto-merge if all conditions met
↓
5. GitHub auto-merges when branch protection satisfied
Race condition handling:
The workflow explicitly checks for the existence of changeset files to ensure dependabot-changesets.yaml has completed before enabling auto-merge. If no changeset is found, auto-merge is skipped and logged.
Workflow outputs and visibility#
On successful auto-merge enablement:
The workflow adds a comment to the PR with details:
✅ Confirmation that auto-merge was enabled
📋 Update type (e.g.,
version-update:semver-minor)📦 List of updated dependencies
ℹ️ Explanation of next steps (waiting for branch protection)
When auto-merge is skipped:
The workflow logs the skip reason to the GitHub Actions console:
Major version update requiring manual review
Changeset not yet created (workflow may retry on next push)
Example comment:
🤖 **Auto-merge enabled**
This Dependabot PR has been configured for auto-merge with squash strategy:
- ✅ CI passed
- ✅ Changeset detected
- ✅ Update type: `version-update:semver-patch`
- 📦 Dependencies: @types/react, @types/react-dom
The PR will automatically merge once all branch protection requirements are satisfied.
Integration with Dependabot configuration#
This workflow respects the Dependabot configuration in .github/dependabot.yml:
Major version blocking:
The Dependabot configuration already blocks major version updates with open-pull-requests-limit: 0 for major updates. The workflow adds an additional safety layer by checking update-type and explicitly skipping major updates even if they somehow bypass the Dependabot configuration.
Grouped updates:
When Dependabot creates grouped update PRs (e.g., “Update React ecosystem”), the workflow treats the group as a single PR and applies auto-merge if ALL updates in the group meet the criteria (no major versions in the group).
Disabling auto-merge#
To disable automatic merging for Dependabot PRs:
Temporary (single PR):
Add a comment or label to the PR. GitHub will automatically cancel the auto-merge if:
A new review is requested
The PR is marked as “draft”
Labels are added that conflict with branch protection rules
Permanent:
Remove or disable the
.github/workflows/dependabot-auto-merge.yamlworkflow fileDependabot PRs will still receive changeset files but will require manual merge
Reusable workflows#
Squareone uses reusable workflows to share common build logic across multiple workflows.
These workflows are defined with the workflow_call trigger and can be called from other workflows.
build-squareone.yaml — Docker image build#
Purpose: Shared workflow for building and optionally pushing the squareone Docker image.
Called by:
ci.yaml(for branch and PR builds)docker-release.yaml(for release builds)
Inputs:
tag(optional): Tag for the Docker image. If not provided, derived from the git branch name.push(optional, default: true): Whether to push the image to the registry. Set tofalsefor build-only validation.
Secrets required:
SENTRY_AUTH_TOKEN: For uploading source maps to SentryTURBO_TOKEN: For Turborepo remote caching
The workflow uses the lsst-sqre/multiplatform-build-and-push reusable workflow to build multi-platform Docker images (linux/amd64, linux/arm64) and push them to ghcr.io.
Image size reporting:
The workflow includes a report-size job that automatically reports Docker image sizes after successful builds.
This job only runs when inputs.push is true (i.e., when images are actually pushed to the registry).
The job reports sizes for each platform (linux/amd64 and linux/arm64) separately, showing both:
Compressed size: The size of data downloaded from the container registry during image pulls
Uncompressed size: The runtime memory footprint of the image layers in Kubernetes pods
Size reports appear in two locations:
Workflow annotations: Quick-view notices at the top of the workflow run (similar to Docker build summaries)
Job summary: A formatted table in the
report-sizejob summary showing per-platform sizes
The job uses the crane tool to inspect image manifests and configs from the GitHub Container Registry (ghcr.io).
Compressed sizes are calculated from the manifest layer sizes, while uncompressed sizes are extracted from the image configuration history.
Security note: The workflow uses minimal permissions (contents: read) and authenticates to the container registry using the default GITHUB_TOKEN for read-only access.
run-chromatic.yaml — Storybook visual testing#
Purpose: Shared workflow for building Storybook and publishing to Chromatic for visual regression testing.
Status: Infrastructure exists but currently not actively used (calling workflows are disabled).
Called by:
chromatic-squareone.yaml(disabled)chromatic-squared.yaml(disabled)
Inputs:
path(required): Path to the Storybook directory (e.g.,apps/squareoneorpackages/squared)workspaceFilter(required): Turborepo workspace filter (e.g.,squareoneor@lsst-sqre/squared)
Secrets required:
token: Chromatic project tokenTURBO_TOKEN: For Turborepo remote caching
Behavior:
Builds Storybook using Turborepo (manual build instead of letting Chromatic build)
On non-main branches: Publishes to Chromatic for visual regression testing
On main branch: Publishes to Chromatic with
autoAcceptChanges: trueto update baselines
CodeQL security scanning — codeql-analysis.yaml#
The codeql-analysis.yaml workflow performs automated security vulnerability scanning using GitHub’s CodeQL analysis.
Triggers:
push: On pushes to themainbranchpull_request: On pull requests targeting themainbranchschedule: Weekly on Wednesdays at 9:29 UTC (cron:29 9 * * 3)
Languages analyzed:
javascript-typescript: JavaScript and TypeScript codeactions: GitHub Actions workflow files
Permissions required:
actions: readcontents: readsecurity-events: write
The workflow uses GitHub’s codeql-action to initialize CodeQL, autobuild the codebase, and perform security analysis.
Results are automatically uploaded to GitHub Security where they can be reviewed in the Security tab.
This provides automated detection of common security vulnerabilities such as SQL injection, cross-site scripting (XSS), code injection, and other CWE-classified security issues.
Chromatic visual testing workflows (disabled)#
Squareone has infrastructure in place for Chromatic visual regression testing, but these workflows are currently disabled.
chromatic-squareone.yaml#
Status: DISABLED (if: false in workflow)
Purpose: Visual regression testing for the squareone application’s Storybook.
Trigger (when enabled):
push: All branches exceptdependabot/**,renovate/**, and tags
Configuration:
Calls the
run-chromatic.yamlreusable workflowUses
SQUAREONE_CHROMATIC_TOKENsecretTargets the
apps/squareoneStorybook
chromatic-squared.yaml#
Status: DISABLED (if: false in workflow)
Purpose: Visual regression testing for the squared package’s Storybook.
Trigger (when enabled):
push: All branches exceptdependabot/**,renovate/**, and tags
Configuration:
Calls the
run-chromatic.yamlreusable workflowUses
SQUARED_CHROMATIC_TOKENsecretTargets the
packages/squaredStorybook
Re-enabling Chromatic workflows#
To re-enable these workflows:
Remove the
if: falsecondition from the workflow filesEnsure the Chromatic project tokens are configured as secrets:
SQUAREONE_CHROMATIC_TOKENSQUARED_CHROMATIC_TOKEN
Ensure the
TURBO_TOKENsecret is available for remote caching
The workflows will then run on every push to publish Storybook builds to Chromatic for visual regression testing.
Secrets and variables reference#
This section documents all GitHub Actions secrets and variables required for the Squareone workflows.
GitHub Actions secrets#
Secret Name |
Purpose |
Used By |
|---|---|---|
|
GitHub App ID for Squareone CI app (used to generate tokens that trigger workflows) |
release.yaml, dependabot-changesets.yaml |
|
Private key for Squareone CI GitHub App (PEM format) |
release.yaml, dependabot-changesets.yaml |
|
Sentry authentication token for uploading source maps |
ci.yaml (via build-squareone.yaml), docker-release.yaml (via build-squareone.yaml) |
|
Turborepo remote cache authentication token |
ci.yaml, build-squareone.yaml, run-chromatic.yaml |
|
LSST the Docs (LTD) username for documentation deployment |
ci.yaml (docs job) |
|
LSST the Docs (LTD) password for documentation deployment |
ci.yaml (docs job) |
|
Chromatic project token for squareone Storybook (currently unused, workflows disabled) |
chromatic-squareone.yaml (disabled) |
|
Chromatic project token for squared Storybook (currently unused, workflows disabled) |
chromatic-squared.yaml (disabled) |
GitHub Actions variables#
Variable Name |
Purpose |
Used By |
|---|---|---|
|
Turborepo remote cache API endpoint URL |
ci.yaml, build-squareone.yaml, run-chromatic.yaml |
|
Turborepo team/organization identifier |
ci.yaml, build-squareone.yaml, run-chromatic.yaml |
Dependabot secrets#
Important
The following secrets must be configured as Dependabot secrets (in addition to GitHub Actions secrets) for the dependabot-changesets.yaml workflow to function.
Secret Name |
Purpose |
Used By |
|---|---|---|
|
GitHub App ID (same value as Actions secret) |
dependabot-changesets.yaml |
|
GitHub App private key (same value as Actions secret) |
dependabot-changesets.yaml |
Configuration notes#
Configuring GitHub Actions Secrets:
Navigate to repository Settings → Security → Secrets and variables → Actions
Click “New repository secret” or use organization-level secrets
Add each secret with its corresponding value
Configuring GitHub Actions Variables:
Navigate to repository Settings → Security → Secrets and variables → Actions
Select the “Variables” tab
Click “New repository variable”
Add each variable with its corresponding value
Configuring Dependabot Secrets:
Navigate to repository Settings → Security → Secrets and variables → Dependabot
Click “New repository secret” or use organization-level secrets
Add the required secrets (
SQUAREONE_CI_GH_APP_IDandSQUAREONE_CI_GH_APP_PRIVATE_KEY)Set repository access to include the squareone repository
Note
When rotating the Squareone CI GitHub App credentials, both the GitHub Actions secrets AND the Dependabot secrets must be updated.