Skip to content

Deployment Pipelines

Deployment pipelines move reviewed code from a Git branch to the server that matches that branch.

This guide documents the deployment pattern currently confirmed for NASPO ValuePoint. It may inform other NASPO WordPress projects, but each application repository must be verified separately before assuming the same workflow, environment, or deployment behavior applies.

This guide helps teammates understand what happens during a deployment, where configuration is managed, and where to begin when a deployment fails.

Use this page with the Git workflow guide. The Git workflow explains how changes move through dev, staging, and main. This page explains what the deployment automation does after code reaches an environment branch.

Scope

This page covers:

  • GitHub Actions deployment behavior
  • GitHub Environments, variables, secrets, and branch restrictions
  • The Tailscale and SSH connection model used by deployments
  • The shared deployment sequence used by the branch workflows
  • rsync behavior and the .deployignore warning
  • Deployment verification and troubleshooting

This guide complements site-specific runbooks, server setup guides, and credential rotation procedures. For Tailscale server onboarding, see Add a New Server to Tailscale.

High-Level Deployment Flow

At a practical level

  1. A branch receives a commit or a workflow is started manually.
  2. GitHub Actions runs the deployment workflow for the matching environment.
  3. The workflow reads environment-specific variables and secrets from GitHub.
  4. The runner joins Tailscale with the CI tag.
  5. The runner connects to the target server over SSH.
  6. The runner synchronizes repository files to the configured deployment path.
  7. Confirm that both the GitHub Actions workflow and the website completed successfully before promoting additional changes.

Confirmed NVP Pattern

NASPO ValuePoint currently uses this model:

  • One Git branch represents one deployable environment.
  • One GitHub Environment stores the configuration for that environment.
  • The GitHub Environment restricts deployments to the matching branch.
  • The deployment workflow uses the same general sequence for each environment, with environment-specific values supplied by GitHub.
Branch GitHub Environment Deployment behavior
dev dev Automatic when commits are pushed or merged into dev
staging staging Automatic when commits are pushed or merged into staging
main prod Manual from the GitHub Actions tab

The manual production step is intentional. It allows production-ready code to be merged into main and deployed when the timing is appropriate.

GitHub Environments

Each NVP deployment branch has a matching GitHub Environment. Each GitHub Environment has a deployment branch policy that restricts deployments to its associated Git branch. This helps prevent accidentally deploying the wrong branch to an environment.

For NASPO ValuePoint, the environment variables are:

Variable Purpose
DEPLOY_PATH Deployment destination. Currently /var/www/html.
SERVER_HOST Tailscale MagicDNS hostname for the matching server, such as nvp-prod, nvp-staging, or nvp-dev.
SSH_USER SSH deployment user. Currently deploy.

The environment secrets are:

Secret Purpose and storage
SSH_PRIVATE_KEY Private key for the deployment user. Server keys are stored in Keeper.
TS_OAUTH_CLIENT_ID Tailscale OAuth client ID used by GitHub Actions. Stored in Keeper.
TS_OAUTH_SECRET Tailscale OAuth secret used by GitHub Actions. Stored in Keeper.

Do not copy secret values into OSKB, GitHub issues, pull request bodies, chat messages, screenshots, logs, or repository files.

Workflow Files

Active deployment workflow files live in the application repository under:

.github/workflows/

Review those workflow files when changing or validating deployment behavior. OSKB should explain the operational model, but the workflow YAML remains the source of truth for the exact triggers, actions, conditions, and deployment commands.

Shared Workflow Sequence

The NVP branch workflows share most of the same deployment process:

  1. Check out the branch.
  2. Run chetan/git-restore-mtime-action@v1 to restore file modification times from Git history.
  3. Connect the GitHub Actions runner to Tailscale using tailscale/github-action@v4 with tags: tag:ci.
  4. Use SERVER_HOST as the Tailscale MagicDNS destination.
  5. Validate required environment variables and secrets and fail early if anything is missing.
  6. Write the SSH key to the runner and validate it.
  7. Scan and register the SSH host key.
  8. Fail early if SSH preparation or validation fails.
  9. Connect to the server over SSH.
  10. Deploy files using rsync.

The validation steps matter operationally. They are designed to fail before file deployment begins when required configuration, credentials, host key preparation, or SSH setup is not usable.

Tailscale and SSH Model

GitHub Actions does not deploy to the public website URL. The runner connects to the target server through Tailscale.

The deployment workflow:

  • Authenticates the GitHub Actions runner to Tailscale using the Tailscale OAuth credentials stored in the GitHub Environment.
  • Uses the tag:ci Tailscale tag for the runner.
  • Uses the environment's SERVER_HOST value as the MagicDNS destination.
  • Connects over SSH as the environment's SSH_USER, currently deploy.
  • Uses the environment's SSH_PRIVATE_KEY secret for the SSH connection.

Tailscale access grants must allow tag:ci to connect to the target server over SSH. See Add a New Server to Tailscale for the shared server onboarding pattern.

rsync Deployment Behavior

The NVP workflows deploy files with rsync.

The confirmed rsync behavior includes:

  • --chmod to normalize permissions
  • --omit-dir-times and --omit-link-times to avoid conflicts involving directory and symbolic-link modification times
  • --delete, subject to .deployignore

Deployment can delete files from the server

The workflows use rsync --delete. A file under /var/www/html that is not tracked in Git may be removed during deployment unless it is protected by .deployignore.

.deployignore is independent of .gitignore. A file ignored by Git is not necessarily protected from deployment deletion. Confirm the active application repository's .deployignore before placing generated files, uploads, caches, local-only configuration, or maintenance files under the deployment path.

.deployignore exclusions must be confirmed in the active application repository and may vary by repository. A path that is protected in one repository should not be treated as protected in another without checking the file.

Deployment Verification

After a deployment, verify both the automation and the site behavior.

Use the GitHub Actions run to confirm:

  • The expected workflow ran for the expected branch.
  • The workflow used the expected GitHub Environment.
  • Required variables and secrets passed validation.
  • Tailscale setup completed.
  • SSH key preparation and host key registration completed.
  • The SSH connection succeeded.
  • The rsync deployment step completed successfully.

Then verify the site:

  • Open the matching environment URL.
  • Confirm the expected change is visible.
  • Check WordPress Admin if the change affects admin behavior or content management.
  • Check for obvious public errors, missing assets, or unexpected redirects.
  • For production deployments, notify the appropriate stakeholders after verification.

Where to Start Troubleshooting

Start with the failing GitHub Actions step. Based on the current deployment design, these are the first areas to check:

Symptom Where to look first
Missing variable or secret failure The matching GitHub Environment variables and secrets
Deployment blocked for the wrong branch The GitHub Environment deployment branch policy
Tailscale setup fails TS_OAUTH_CLIENT_ID, TS_OAUTH_SECRET, Tailscale OAuth configuration, and tag:ci access
SSH key validation fails SSH_PRIVATE_KEY formatting and the matching server-side deploy key
Host key scan or registration fails SERVER_HOST, Tailscale MagicDNS, and server reachability
SSH connection fails SERVER_HOST, SSH_USER, deploy user's authorized key, and Tailscale grants
Files are missing after deployment rsync --delete, repository contents, and the active .deployignore
Permissions look wrong after deployment The workflow's --chmod settings and server ownership expectations

Avoid troubleshooting by changing production manually unless the incident response requires it. If a server-side change is made during an incident, record it and bring the repository or deployment process back into alignment afterward.

Deployment Setup Checklist

This checklist is a starting point for configuring a new deployment environment. It does not replace a site-specific runbook or validation of the target repository, server, and GitHub Environment.

  1. Create a deploy user on the server for CI only.
  2. Lock the deploy user's password.
  3. Verify the deploy user and group membership.
  4. Create a dedicated SSH key for deployment.
  5. Add the public key to the server.
  6. Fix ownership and permissions for the key.
  7. Test SSH as the deploy user.
  8. Test WP-CLI as the deploy user when the deployment or maintenance process depends on WP-CLI.
  9. Test write access only where deployment needs it.
  10. Fix paths that fail validation.
  11. Set up the matching GitHub Environment with variables and secrets.
  12. Confirm Tailscale grants allow CI access to the server.
  13. Create or update the relevant workflow file under .github/workflows/.
  14. Commit the workflow.
  15. Run the workflow manually or through the branch trigger, depending on the environment.

Use site-specific values when performing these steps. Do not reuse example hostnames, key names, or paths without confirming they apply to the target site.