Your hosted pipeline can deploy code, yet still lack a rollback you can run under pressure. Rebuilding from a branch or SSHing into a VPS makes recovery an untested production change. A failed release can turn minutes of downtime into hours. The previous artifact, database state, network access, and permissions must reverse together.
Reliable CI/CD and deployment workflows for hosted environments need matched delivery controls. Promote one immutable artifact and keep measurable rollback paths.
Choose CI/CD by hosting destination
Choose the deployment method from the hosting target backward. Static hosting needs a published bundle. A VPS needs a controlled server release. A PaaS or cloud service usually needs an API call or image reference.
Use this default: Build once, scan once, sign once, then promote the same version. Put deployment access in a dedicated identity. Keep production off public pull-request runners. Pick a release pattern based on the downtime your business can absorb.
Match the target to the release method
A shared hosting account or Cloudflare Pages site usually needs a static bundle and provider-managed publishing. A virtual private server, or VPS, is a server slice where you hold root-level responsibility.
A VPS needs repeatable releases, disk checks, and a clear restore path. Think of it like renting a shop space. The landlord supplies the building, but you manage the locks and stock.
| Hosted target | Artifact to promote | Best delivery path | Typical rollback | Operations load |
|---|
| Static hosting | Hashed site bundle | Provider API or Git integration | Previous published build | Low |
| VPS app | Release tarball or image digest | SSH deploy account or controller | Previous release symlink | Medium |
| Docker Compose VPS | Docker image by digest | Registry pull and compose up | Prior image digest | Medium |
| PaaS or serverless | Bundle or container image | Scoped provider API | Prior revision | Low to medium |
| Kubernetes cluster | Signed image digest and manifest | GitOps controller | Prior ReplicaSet or manifest | High |
Use rsync only with atomic releases
rsync copies changed files between machines. It remains reasonable for a low-risk PHP or static app, but it must copy into a versioned folder first.
Switch a current symlink only after checks pass. A symlink is a pointer, like a sign that directs visitors to one store room. This avoids overwriting files in the live folder.
Let docker compose earn its simplicity
Docker Compose is often easier than Kubernetes for apps with one to five services. This is common on DigitalOcean, Linode, or Vultr. A small team can reproduce images, ports, volumes, and health checks on a replacement VPS.
The target sets the delivery method. The next choice is whether the runner can reach it safely.
A good deployment workflow changes with the destination. It should not force every host through SSH. For static hosting, build a hashed bundle and upload it through the provider API.
Keep the prior published version. For a simple VPS release, upload a versioned tarball with rsync --delay-updates. Run a health check inside that release folder. Switch the current symlink only after it passes.
That creates atomic releases without overwriting the live folder. For Docker Compose, pin an image digest in an environment file. Pull that digest and run docker compose up -d.
Check the service before removing the prior image. A Kubernetes release should update a manifest with the approved digest. A GitOps controller then applies that desired state.
A PaaS workflow should promote a provider revision through its scoped API. The runner boundary determines whether this path stays safe.
Choose runners by network boundary
GitHub-hosted runners are the best default for targets with public API calls. Self-hosted runners fit private networks, IP allowlists, or internal registries.
Use GitHub-hosted runners for tests, Docker image publishing, and deployments through cloud APIs. This includes Amazon Web Services, Microsoft Azure, Google Cloud, and DigitalOcean. These runners are disposable machines.
Disposal limits persistence after a job ends. That matters when a build script or dependency is compromised.
| Option | Setup time | Private subnet access | Routine maintenance | Best fit |
|---|
| GitHub-hosted runner | Minutes | No, by default | Provider-managed | Public APIs and registries |
| GitLab shared runner | Minutes | No, by default | Provider-managed | GitLab-centered teams |
| Ephemeral self-hosted runner | Hours to days | Yes | Image patching and isolation | Private cloud networks |
| Jenkins worker | Days | Yes | High | Existing Jenkins estates |
Private access needs isolated runners
Run an ephemeral self-hosted runner in a separate subnet for private access. It can reach a private Kubernetes cluster, package registry, or migration endpoint. Ephemeral means the runner is destroyed after the job.
Think of it as renting a fresh car. You return it after each trip instead of leaving one unlocked at the airport.
Production servers must not run CI jobs
Never install a general self-hosted runner on a production server. Pull requests can run project scripts. Compromised dependencies can then execute code on the live host.
With access contained, a subtler risk remains: staging and production can receive different software.
GitHub Actions and GitLab CI both work well when code already lives on those platforms. Runner price is only one part of the choice. Compare included and extra CI minutes, parallel-job limits, and artifact storage.
Also count container registry storage and time spent maintaining runner images. Hosted runners offer clearer isolation. Each job starts on provider-managed ephemeral systems.
Hosted runners usually cannot reach a private subnet directly. Self-hosted runners can reach private registries and internal deployment endpoints. They also need patching, network separation, restricted labels, and teardown.
Use hosted runners for builds and public APIs when possible. Use a narrow ephemeral runner only for the private deployment step.
Build one versioned artifact once. Promote that exact byte-for-byte output through development, staging, and production.
Rebuilds break release traceability
The most frequent error is rebuilding a Docker image after staging approval. That breaks the audit trail. Incident review then becomes guesswork.
The approved object is no longer the deployed object. A release digest should identify the exact image that passed staging.
GitOps means Git stores the desired deployed state. That state often includes an image digest in a manifest. A promotion pull request changes sha256:old to sha256:new.
It does not rebuild the application. The same image moves forward through each environment.
Sign and verify what you ship
Dependency and image scans should block known critical issues with safe patches. Sign the artifact and verify its signature before release. This helps stop a registry tag like latest from changing unexpectedly.
One artifact, three controlled promotions
1. Build
commit + digest
→
2. Scan and sign
SBOM retained
→
3. Staging
same digest
→
4. Production
verified digest
A trusted artifact removes drift. Now choose how much live traffic should see it first.
Match release strategy to outage risk
Use rolling releases for compatible stateless instances. Use blue-green for fast traffic reversal. Use canary releases for measurable high-impact changes. Use feature flags for code that must stay off at first.
Rolling releases fit compatible services
Block the next batch when HTTP 5xx errors exceed the normal rate. Also block it when p95 response time exceeds its agreed limit for 2 to 5 minutes. Health checks must test useful work.
A TCP port reply alone is not enough. It only proves that a process answered.
Blue-green buys fast reversal
Blue-green keeps old and new environments ready at the same time. The load balancer switches traffic after checks pass. It can return traffic to the old environment within seconds or minutes.
Blue-green needs enough capacity for two live environments. This is often too costly for a small single-VPS application.
Canary needs enough real traffic
Feature flags often give smaller traffic sites more value. They deploy dormant code first. Staff or a narrow user group can then enable it.
If you are choosing a VPS or managed platform, test its release needs first. Can it run a second instance, expose health checks, and retain the prior artifact? Those questions prevent buying a plan that misses your recovery target.
Release patterns reduce blast radius. Credentials decide whether the pipeline can safely use those patterns.
A canary should move through planned stages. Do not send an arbitrary small share of traffic to a new release. Start with staff or 1% of requests.
Increase to 5%, 25%, and 100% only after each observation window passes. Check HTTP 5xx rate, p95 latency, saturation, and a business event. That event could be a successful checkout or login.
Halt promotion if canary errors rise 0.5 percentage points above the stable version for five minutes. Also halt if checkout completion falls below its normal range. These limits turn debate into a recorded release rule.
The deployment system should then return traffic to the stable revision. Keep canary logs, traces, and the image digest for review. Recovery becomes a measured control rather than an incident judgment call.
Replace SSH keys with short-lived identity
Use OIDC-issued temporary credentials and least-privilege roles when the provider supports them. Keep SSH for limited legacy VPS releases.
OIDC reduces secret exposure
A static SSH key is like a master key copied into a locked drawer. A repository secret hides it from normal output, but it remains valuable for as long as it works.
OIDC lets a CI job request short-lived cloud credentials. The provider checks the repository, branch, and workflow claims first. The credentials expire after the job.
Lock down unavoidable VPS SSH
A typical failure looks like this: one shared SSH key has full root access, and a former contractor still holds a copy.
Every repository uses that same key. Dedicated accounts and server-side logs make this exposure visible. They also make it easier to remove.
Require supply-chain checks
Secrets management stores runtime secrets in a provider vault or encrypted environment store. It does not bake them into images. HashiCorp Vault and major cloud secret services can issue credentials at runtime.
They also create access logs. This gives teams a record of who requested production access.
Secure identity protects production entry. The host still needs basic safeguards for a bad release.
Prepare the host before deployment automation
Do not automate production releases before the host has health checks and central logs. It also needs monitoring, tested backups, resource limits, separate configuration, and a proven restore process.
Make these checks block a release
Monitor error rate, p95 latency, queue depth, and failed jobs. The DORA research program popularized four delivery measures. They are deployment frequency, change lead time, change failure rate, and recovery time.
Track those measures across 30 to 90 days. A one-week number can hide a rare but serious failed release.
Separate configuration from application code
Keep environment-specific settings outside the application artifact. Store non-secret settings in versioned deployment configuration. These include service endpoints, feature defaults, and resource limits.
Get passwords, tokens, and certificates from a secret manager at runtime. Development, staging, and production may use different values. The artifact and its image digest should not change.
Otherwise, an environment setting can become an unreviewed code change. This works well in theory, but mixed configuration files often defeat it in practice.
Prove backups can restore
A scheduled backup is not a recovery plan until you restore it. This is especially true on managed hosting plans. Database and file retention can follow separate policies.
Test a restore into an isolated environment. Measure both the restore time and the data age you recover.
This automation level is not a priority for a personal static site with rare changes. That site may already publish safely through a managed provider. Self-hosted runners, Kubernetes, and canary releases are poor choices when maintenance costs exceed the outage risk they reduce.
Measure recovery, not just deployment speed
A rollback is reliable only when the prior immutable artifact is ready. Failure limits must be clear. Traffic must return through automation or one approved action.
Set thresholds before the release
Use application monitoring, synthetic checks, and logs together. A successful HTTP 200 response does not prove the checkout works. It also does not prove login, email queues, or background workers work.
Set limits before users see the release. This prevents a stressed team from inventing acceptable failure levels during an outage.
Make database changes reversible
Use expand-and-contract migrations for database changes. Add compatible fields first. Deploy code that supports both forms.
Move data after that. Remove old fields in a later release. This prevents a rollback where old code cannot read a changed schema.
Rehearse the whole reversal
A rollback drill should check image availability and configuration compatibility. It should also check DNS or load-balancer switch time. Include cache behavior and customer notice steps.
Run one before a major migration. Record the real recovery time, not the hoped-for time. A common case is a rollback that restores code quickly but leaves an incompatible database change.
The goal is not perfect automation. The goal is a bad change with a small, measured, reversible effect.
Common questions
What is a CI/CD deployment workflow?
A CI/CD workflow tests, builds, approves, and deploys Git changes to a hosted environment. Continuous Integration checks changes often. Continuous Delivery keeps a tested release ready for approval.
How do I deploy to a VPS with GitHub Actions?
Deploy to a VPS by building one artifact and transferring it through a restricted deploy account. Check health, then switch to the new release atomically. Use a separate runner or narrow SSH path, never a general runner on production.
What is the difference between continuous delivery and continuous deployment?
Continuous Delivery prepares each validated change for release. Continuous Deployment releases each validated change automatically. Most small businesses start with approval before production and automate later.
Should I use a self-hosted runner for deployments?
Use a self-hosted runner only for private networks, IP allowlists, or internal services. Make it ephemeral. Keep it away from production servers and database subnets.
How do I securely store secrets in a CI/CD workflow?
Store runtime secrets in a cloud secret manager and use OIDC temporary credentials when possible. If SSH is required, use a dedicated non-root account and host-key checks. Restrict rights and rotate keys every 90 to 180 days.
How do I roll back a failed deployment?
Roll back by redeploying the prior immutable artifact or returning traffic to the prior healthy environment. Trigger that action from clear error, latency, or transaction-failure limits. Do not wait for a full manual diagnosis.
The essentials:- Choose a deployment path that matches the hosting target and its recovery needs.
- Build, scan, sign, and promote one immutable artifact through every environment.
- Use short-lived identity, isolated runners, and least-privilege deployment access.
- Test rollback, backup restore, and database compatibility before an incident forces the issue.
Related sources
These articles can help you explore the topic in more depth: