GitHub Actions is CI that lives next to the code, which is most of its appeal
and all of its risk. A workflow file is code that runs with access to your
secrets, and it is edited by everyone who can open a pull request.
The useful pipeline shape for a self-hosted application is three jobs chained
with needs: test, build and push an image, deploy. Each runs only if the
previous passed, and what gets deployed is the exact artifact that was tested —
tagged with the commit SHA, not latest.
Reach for Actions when the repository already lives on GitHub and the pipeline
is straightforward. Reach for something else when builds need hardware the
hosted runners do not have, or when minutes cost more than a small self-hosted
runner would.
Four settings matter more than the rest. Per-job permissions, so a test job
cannot push packages. A workflow-level concurrency group, so two deploys never
run at once. Environment secrets with a required reviewer, so an accidental merge
becomes a prompt instead of a release. And SHA-pinned third-party actions, so a
moved tag cannot swap in new code that reads your secrets.
The failure that costs the most time is a deploy step that exits zero while the
application is broken — docker compose up without --wait, or services with
no health check for it to wait on. A smoke test after the deploy step turns that
into a red build instead of a silent outage.
Articles here show complete workflow files rather than fragments, and state
which runner image they were verified on.
Test, build a container image, push it to GHCR and deploy it to a VPS over SSH — with the concurrency guard, environment approval and token scoping that keep the pipeline from becoming the weak point.
Intermediate · 4 min read
Beginner to advanced
A reading order that builds Actions knowledge in the sequence it is actually needed.
Test, build a container image, push it to GHCR and deploy it to a VPS over SSH — with the concurrency guard, environment approval and token scoping that keep the pipeline from becoming the weak point.
4 min
AI-assisted
Frequently asked questions
Why pin actions to a commit SHA instead of a tag?
Tags are mutable. The author can move `v3` to different code at any time, and that code runs with access to the secrets available to its job. Pin the SHA and let Dependabot raise the update as a reviewable diff.
Repository secrets or environment secrets?
Environment secrets for anything that touches production. They are only readable by jobs that declare the environment, and environments support required reviewers and wait timers.
What is the most dangerous workflow misconfiguration?
`pull_request_target` combined with a checkout of the pull request head. It runs untrusted code with write-scoped secrets, and it is the pattern most commonly exploited in the wild.