GitHub And Open Source Collaboration

GitHub Actions Deployments To Remote Servers

A deployment workflow is not just a longer CI workflow. CI asks whether a change is acceptable to merge. Deployment changes a running system. That difference changes the security model, the review standard, the failure handling, and the evidence required after the workflow says it succeeded. A GitHub Actions job that connects to a remote server can update files, restart services, run migrations, clear caches, or expose a broken release to users. Treat it as production automation, not a convenient shell shortcut.

Use GitHub's official documentation for current platform behavior. GitHub documents deployment environments, GITHUB_TOKEN authentication, and deploy keys. Check the current docs before teaching exact permission names, plan availability, environment protection options, or token behavior. The stable lesson is the design model: deployment workflows need explicit triggers, restricted credentials, protected environments, idempotent scripts, verification, and recovery.

The Deployment Boundary

A remote-server deployment has at least three systems: GitHub, the runner, and the server. GitHub stores the workflow and secrets. The runner executes the job. The server receives the deployment action. A safe design names the authority at each boundary. Who can edit the workflow? Who can approve production? Which credential lets the runner reach the server? Which user on the server performs the update? Which service manager restarts PHP-FPM, a queue worker, or a web server?

Do not let a deployment workflow grow from copied commands without this model. A command such as SSH deploy@example.com git pull hides many assumptions. Which branch is being pulled? What happens if Composer install fails? Are migrations run before or after code switches? Can two deployments overlap? Which version is now serving? How does the workflow know the application is healthy? How does the team roll back?

A deployment lesson should teach learners to answer those questions before writing YAML. The workflow is only the remote control. The release process is the system being controlled.

Triggers And Environments

Deployment triggers should match the project's release policy. A hobby app might deploy on every push to main. A production business application may deploy only after a tag, release, manual workflow dispatch, or merge to a protected branch. The important rule is that deployment should happen from a state the team considers releasable.

GitHub environments help model deployment targets such as staging and production. Jobs that reference an environment can use environment-specific secrets and can be protected by rules such as required reviewers, branch restrictions, and wait timers where available. This matters because production secrets should not be available to every workflow run. A test job on a forked pull request should not be able to reach a production server.

A typical high-level deployment flow is:

name: Deploy

on:
  workflow_dispatch:

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
      - name: Deploy over SSH
        run: ./deploy/remote-deploy.sh

This example is not complete enough to paste into production. It shows structure: a manual trigger, a production environment, narrow repository permissions, checkout, and a deployment script kept in the repository. The real workflow must add credential setup, validation, logging, and error handling appropriate to the project.

Credentials And Least Privilege

Deployment credentials should be scoped to the deployment job and the target environment. A production SSH key should not be available to test jobs. A staging key should not deploy production. A credential that can log into a server as root is usually too powerful for application deployment. Prefer a dedicated deploy user with restricted permissions, a clear home directory, limited writable paths, and only the commands required for release.

A remote server may need to fetch code from GitHub. There are several patterns. The runner can build an artifact and copy it to the server. The server can pull from GitHub using a deploy key. The workflow can connect over SSH and run a release script. A container registry can hold an image that the server pulls. Each pattern has different credentials and rollback behavior.

Deploy keys are SSH keys associated with a repository. They can be useful when a server needs read-only access to one repository. They are not a universal answer. If one server deploys many repositories, if write access is needed, or if organization-wide automation is required, a GitHub App, machine user, package registry credential, or Actions-managed artifact may fit better. The plan should explain why the chosen credential exists and how to rotate it.

Never store private keys in the repository. If a workflow needs a private key, use GitHub environment secrets or another appropriate secret store. Avoid printing key material or command environments. If a credential leaks, rotate or revoke it before cleaning logs or history.

Idempotent Remote Scripts

A deployment script should be safe to rerun after partial failure. That does not mean every command can be repeated blindly. It means the script has a clear model for current release, next release, shared directories, migrations, cache, services, and locks. If the workflow fails after uploading files but before switching the live symlink, rerunning should not corrupt the application. If it fails after switching code but before restarting workers, the next run should detect and repair the state.

For PHP applications, a remote deploy often needs to install dependencies, build or receive assets, update a release directory, run migrations, warm caches, switch the active release, reload PHP-FPM or web workers, and restart queue workers. Running all of that in the server's live directory is risky because users can see a half-updated application. A release-directory approach can reduce that risk: prepare a new release in a separate directory, then switch a symlink when preparation succeeds.

Overlapping deployments are another failure mode. Two workflow runs can race: one starts deploying commit A, another starts deploying commit B, and their remote commands interleave. Use GitHub Actions concurrency controls, remote deployment locks, or both. The workflow should decide whether newer deployments cancel older ones or wait for them. For production, the decision should be explicit.

Post-Deploy Verification

A deployment is not successful merely because the SSH command returned zero. It is successful when the intended version is serving and the system remains healthy after traffic returns. The workflow should verify the release after the switch. Verification can include checking a version endpoint, running smoke tests against critical routes, confirming migrations are applied, checking queue workers, inspecting service status, and reading logs or error trackers for immediate failures.

Good verification is specific. curl https://example.com proves little if the home page is cached. A better smoke check might request /health, /login, a lightweight API endpoint, and a version endpoint that returns the deployed commit or release ID. For a PHP application with workers, check that the worker process restarted and can process a small safe job or that queue depth is not climbing unexpectedly.

Add a watch window when the risk justifies it. Many deployment failures appear only after real traffic exercises the new code. A workflow can wait a short period and check error rate, health endpoint status, or external uptime monitor state. More advanced monitoring may happen outside Actions in Sentry, Datadog, Prometheus, or a hosting provider. The lesson is that deployment evidence includes runtime health, not only command success.

Rollback And Roll Forward

Before deploying, define what failure means and what response is allowed. If smoke tests fail before traffic is switched, abort and leave the old version running. If smoke tests fail after the switch, the workflow may roll back to the previous release or stop and alert a human. If a database migration is irreversible, rollback may not be safe and the team may need a roll-forward fix. This must be known before the incident.

Rollback is easier when releases are immutable and previous releases are retained. It is harder when deployment modifies the live directory in place. Database changes require special care: backward-compatible migrations, expand-and-contract releases, and feature flags can make rollback possible. A workflow that blindly runs destructive migrations and then says rollback is misleading.

Reporting is part of recovery. A failed deployment should notify the right channel with the environment, commit, workflow run, failing step, suspected impact, and next action. Silent failure is dangerous. A green workflow with a down site is worse because it teaches the team to trust the wrong signal.

Deployment Audit Trails

A useful deployment leaves an audit trail. The workflow run should show who triggered the release, which commit or tag deployed, which environment was targeted, which approval was required, and which verification checks passed. The server should also record the deployed release identifier in a file, log line, or version endpoint. When an incident happens, responders should not have to guess whether the code running on the server matches the green workflow run.

Review Checklist

Review deployment workflows as security-sensitive code. Check the trigger, environment protection, branch/tag restrictions, permissions, secrets, runner choice, remote user, deploy script, concurrency behavior, verification checks, logging, notification path, and rollback policy. Confirm that production credentials are not available to untrusted pull request code. Confirm that a workflow editor cannot silently bypass production approval unless that is a deliberate admin policy.

The learner should be able to design a GitHub Actions deployment workflow that uses protected environments, least-privilege credentials, explicit release triggers, idempotent remote scripts, post-deploy verification, and clear failure reporting. They should also be able to explain why a deployment is not complete until the running application proves it is healthy.

Practice

Review a deploy workflow

A production workflow runs on every push to any branch and has access to PRODUCTION_SSH_KEY. It SSHes as root and runs git pull && composer install && systemctl restart php-fpm in the live application directory.

List at least six risks and propose safer alternatives.

Show solution
  • It deploys from any branch. Restrict production deployment to protected branches, tags, releases, or manual dispatch with approval.
  • Production secrets are too broadly exposed. Use a protected production environment and expose secrets only to the deployment job.
  • SSH as root is too powerful. Use a dedicated deploy user with least privilege.
  • It updates the live directory in place. Prepare a separate release directory and switch only after preparation succeeds.
  • There is no concurrency control. Use workflow concurrency and/or a remote deploy lock.
  • There is no verification. Add version checks, smoke tests, service checks, and log/error monitoring.
  • There is no rollback plan. Retain previous releases or define a roll-forward process.

The safer design treats deployment as a controlled release process, not a remote shell shortcut.

Design post-deploy checks
  • a public website;
  • a JSON API;
  • database migrations;
  • a queue worker;
  • Sentry or another error tracker.

List the checks and what each one proves.

Show solution
  • Version endpoint: proves the expected commit or release is serving.
  • Public page smoke check: proves the web server and PHP entry point respond.
  • API smoke check: proves JSON routing, authentication boundary if safe, and response shape still work.
  • Migration check: proves the expected schema version is applied.
  • Worker status check: proves queue workers restarted or are processing jobs.
  • Queue depth check: detects workers failing after deployment.
  • Error tracker check: detects immediate spikes or new fatal errors.
  • Short watch window: proves health remains stable after real traffic returns.

Each check should have a failure threshold and a response: abort, roll back, roll forward, or alert a human.

Choose a credential pattern
  1. A server needs read-only access to one private repository.
  2. A GitHub Actions workflow builds an artifact and copies it to one production server.
  3. A deployment needs short-lived access to a cloud provider.

Explain the tradeoff and one rotation concern for each.

Show solution
  1. A read-only deploy key may fit because access is limited to one repository. Rotation concern: know which server owns the private key and remove it when the server is retired.
  2. An environment-protected SSH key or deployment credential may fit if the workflow must copy artifacts directly. Rotation concern: store it as an environment secret, document ownership, and test replacement.
  3. Prefer OIDC or another short-lived identity flow when the provider supports it. Rotation concern: fewer static secrets need rotation, but the trust policy must be reviewed and maintained.

The pattern should minimize long-lived broad credentials and make ownership clear.