October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
VGSources
Blog

How to Deploy to a VPS With GitHub Actions

Learn how to deploy reliably from GitHub Actions to a Linux VPS. Compare hosted-runner SSH with self-hosted runners, configure least-privilege access, publish immutable releases, verify health, and roll back safely.
Length12 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a GitHub-hosted runner to test and build your code, then connect to the VPS over SSH and activate a versioned release. Keep the SSH key and production credentials in GitHub Actions secrets, deploy only from a protected branch or approved environment, run a health check, and retain the previous release for rollback. A self-hosted runner on the VPS is an alternative when you need direct Docker, filesystem, or service-manager access, but it makes the server part of your CI execution environment and increases your maintenance and security responsibility.

Choose the deployment architecture first

There are two sound ways to move a repository to a Linux VPS with GitHub Actions. The safer default for most teams is a GitHub-hosted runner that connects to the VPS for the short deployment window. The other option is a self-hosted runner installed on the VPS, where the job executes locally.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Decision axis Hosted runner plus SSH Self-hosted runner on the VPS
Network path The VPS accepts an SSH connection from the GitHub runner’s egress address. The runner on the VPS makes outbound connections to GitHub and executes the job locally.
Maintenance GitHub maintains the runner image; you still patch and harden the VPS. You patch and harden the operating system, runner application, language runtimes, Docker, and every other installed dependency.
Isolation The build runs on GitHub-hosted infrastructure, separate from the production host. The job runs in the VPS environment. A deployment environment does not put a self-hosted job in a container automatically.
Local access Artifacts or images must be transferred or pulled over the network. The job can access local Docker sockets, filesystems, and service managers directly.
Typical failure modes SSH reachability, host-key validation, key rotation, or artifact transfer errors. Runner downtime, insufficient host capacity, queued jobs, stale software, or compromise of the runner host.

When hosted SSH is the better default

Choose this model when you want short-lived build machines, a small trust boundary on the production server, and a clear separation between compilation and serving traffic. The VPS needs only an SSH service and a deployment account. The workflow can build once, upload one immutable artifact, and ask a small remote script to activate it.

When a self-hosted runner is justified

Use a self-hosted runner when the build or deployment must use resources that cannot be exposed to a hosted runner, such as a local Docker registry, a private network, large caches, or a service manager that is reachable only from the VPS. GitHub defines a self-hosted runner as a system you deploy and manage to execute GitHub Actions jobs. The runner application must be running on the host to accept jobs. That convenience comes with responsibility for patching, log retention, capacity, repository access, and incident response.

#1 Best Overall
Sale
Gaoye Blue Light Blocking Glasses
  • BLUE LIGHT BLOCKING GLASSES: Our blue light glasses for women boast UV400 protection, blocking harmful blue rays from computer screens. With these bluelight glasses women, you can enjoy gaming, reading, or watching TV with no worries of eye strain.
  • EYE RELIEF: Our blue light glasses are engineered to reduce blurry vision or strain caused by prolonged screen time. These womens blue light glasses aid in better sleep by preventing blue light from disrupting your natural sleep cycle, helping you fall asleep faster and enjoy a more restful night's sleep, making them the perfect christmas present.
  • COMFORTABLE: Crafted from durable material, our trendy blue light glasses for woman or blue light glasses men ensure long-lasting comfort. The high-transmittance lenses accessories, gamer gifts for men, help keep your eyes relaxed. Perfect stocking stuffers, gifts for gamers men, christmas gifts for men, halloween costumes.
  • LIGHTWEIGHT: These clear blue light glasses feature a lightweight frame, meaning they’ll stay comfy for long-term wear. Perfect as blue blockers glasses for women or mens blue light glasses, these non-prescription fake glasses for men can be worn throughout the day. The blue light blocking glasses women feature polycarbonate HD lenses, which are anti-reflective and restore true color.
  • WORRY-FREE PURCHASE: If you experience any issues, our support team is ready to assist you quickly. These are the perfect costume glasses for cosplay dressing up.

Prepare the VPS for repeatable releases

Perform these tasks once as an administrator, then use the limited deployment account for routine releases.

  1. Create a non-root account. Add a user such as deploy, give it only the groups and commands required to install or activate your application, and disable direct root SSH login. Do not use a root private key in GitHub.
  2. Install the runtime. Install the language runtime, web server, process manager, or Docker version required by the application. Record the versions so the build and production environments do not drift silently.
  3. Define the service. Create a systemd unit or equivalent service definition with a stable name, for example myapp.service. Keep runtime configuration outside the release archive, and load it from a root-readable or deploy-readable location with restrictive permissions.
  4. Create release directories. A useful layout is /srv/myapp/releases for unpacked versions, /srv/myapp/current as a symlink to the active version, and /srv/myapp/shared for persistent uploads, sockets, or configuration. Give the deploy account ownership only where it needs to write.
  5. Restrict inbound traffic. Allow only the SSH port and the application ports that must be reachable. A reverse proxy can expose HTTPS while keeping the application process bound to localhost.
  6. Make the service restartable. Verify that an administrator can run systemctl restart myapp, inspect systemctl status myapp, and read the service logs before connecting GitHub Actions.

Use a deployment script instead of a long SSH command

Put deployment logic in a root-owned script or in a script whose permitted commands are tightly constrained. Keeping it on the server makes the workflow small and lets you test the exact production operation manually.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#!/usr/bin/env bash
set -euo pipefail

archive=$1
release_id=$2
app=/srv/myapp
release_dir=$app/releases/$release_id

case $release_id in
  (*[!a-zA-Z0-9._-]*|'') echo 'invalid release id' >&2; exit 2;;
esac

mkdir -p $release_dir
tar -xzf $archive -C $release_dir
ln -sfn $release_dir $app/current
systemctl restart myapp
systemctl is-active --quiet myapp
rm -f $archive
find $app/releases -mindepth 1 -maxdepth 1 -type d -printf '%T@ %pn' | sort -nr | tail -n +6 | cut -d' ' -f2- | xargs -r rm -rf

Install the script at a path such as /usr/local/bin/deploy-myapp, make it executable, and test it with a known archive. The release identifier should be the commit SHA or another immutable value, not a mutable label such as latest. The cleanup command above retains the five newest release directories; choose a retention count that fits your disk and rollback policy.

Rank #2
Windex Gaming Cleaning Wipes, Pre-Moistened Wipes to Instantly Clean all Gaming Gear and Electronics, Resealable Packaging for a Freshness Lock, 25 count (Pack of 1)
  • WINDEX GAMING WIPES: effortlessly clean all your gaming gear and gaming electronics in one easy step with our new Windex Gaming Wipes
  • AMMONIA FREE: our ammonia-free formula is safe to use on all of your favorite gaming equipment like consoles, headsets, controllers, screens, keyboards, and more
  • PRE-MOISTENED WIPES: each wipe instantly removes dust, dirt, and stubborn smudges and fingerprints leaving a streak-free shine
  • ANTI-STATIC: great for cleaning and wiping down your gaming gear and electronics leaving behind no visible residue
  • COMPACT RESEALABLE PACKAGE: store your pack of Windex Gaming Wipes anywhere in your home for whenever a quick and easy wipedown is needed

Create a dedicated SSH identity and secrets

Generate a key used only by this repository and deployment purpose. Keep the private half in GitHub Actions secrets or an environment secret, and place the public half in the deploy user’s authorized_keys.

  1. Generate an ed25519 key pair on an administrator workstation without replacing an existing personal key.
  2. Append the public key to the deploy account’s ~/.ssh/authorized_keys. Set the directory to mode 700 and the file to mode 600.
  3. Where practical, restrict the key entry with an allowed source range, a forced command that accepts only the deployment operation, and options such as disabling agent forwarding, port forwarding, and pseudo-terminals.
  4. In the repository, open Settings, then Secrets and variables, then Actions. Add secrets for the private key, VPS host, SSH port, deploy user, and any registry or application credentials. Put production-only values in a GitHub deployment environment named production rather than in general repository secrets.
  5. Store the VPS host key as a secret containing the exact line or lines from a trusted known_hosts file. This lets the runner verify the server identity without silently trusting a first connection.

Never put a private key, password, token, or runtime secret in workflow YAML, source code, an artifact, or a command that will be printed to logs. GitHub’s GITHUB_TOKEN is intended for workflow-to-GitHub authentication and expires after 60 minutes; it is not a replacement for a separately scoped VPS credential.

Build once and publish an immutable artifact

The workflow should run tests and the production build exactly once. Upload the resulting archive, or publish a container image tagged with the full commit SHA. Do not make an unqualified latest tag the only reference to a release: a retry could pull different bytes than the first deployment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
Sale
LISEN Cell Phone Stand Phone Holder for Desk, Office Decor Gadgets for Men
  • Ergonomic & Adjustable Design: Fight "text neck" and improve your posture. Designed for comfort, it offers an adjustable height (7.1in-8.5in) (1.4in adjustable range) and angle (5°-85°) Please note: cell phone stand is only 9 inches tall—the length of a standard pencil
  • Without blocking subtitles Perfect for office desk Tiktok Face Time Zoom video—this is the ultimate kitchen desk essential for phones! Perfect viewing experience while reducing neck back strain, A must-have for gamers streamers desk, and anyone who values both style and support!
  • Sleek & Stable Design fits Otterbox Case: Say goodbye to bent cables, tangles, and tip-overs! LISEN’s stylish adjustable desk phone holder features a built-in charging hole and smart cable management, keeping your phone secure and your workspace clutter-free cool gadgets for men
  • ANTI-SLIP DESIGN free hands: The under 10.00 items easy gadgets back and the bottom of this kitchen essentials for women are fully covered by anti-skid silicone, which LISEN stand up desk decor can provide maximum protection device from any scratches and slides
  • Your Perfect Viewing Companion:If your product arrives damaged or breaks within 365 days, reach out to LISEN for a quick and hassle-free solution. Struggling with assembly, like tightening the screw? We’ve got your back! Your experience matters to us. (Product including: phone Holder stand*1, screw*1, hexagon screwdriver*1, specification*1)

The commands below show a Node-style application. Replace the dependency, test, and build commands with the equivalent commands for your runtime, while preserving the same sequence: checkout, install, test, build, package, deploy.

name: Deploy to VPS

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Check out source
        uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build production files
        run: npm run build

      - name: Package the release
        run: tar --exclude=.git --exclude=node_modules -czf release-${GITHUB_SHA}.tar.gz .

      - name: Install SSH client
        run: sudo apt-get update; sudo apt-get install --yes openssh-client

      - name: Configure SSH
        env:
          SSH_PRIVATE_KEY: ${{ secrets.VPS_SSH_PRIVATE_KEY }}
          SSH_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
        run: |
          install -m 700 -d ~/.ssh
          printf '%sn' "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          printf '%sn' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
          chmod 600 ~/.ssh/known_hosts

      - name: Upload release
        env:
          VPS_HOST: ${{ secrets.VPS_HOST }}
          VPS_PORT: ${{ secrets.VPS_PORT }}
          VPS_USER: ${{ secrets.VPS_USER }}
        run: scp -P "$VPS_PORT" release-${GITHUB_SHA}.tar.gz "$VPS_USER@$VPS_HOST:/tmp/release-${GITHUB_SHA}.tar.gz"

      - name: Activate release
        env:
          VPS_HOST: ${{ secrets.VPS_HOST }}
          VPS_PORT: ${{ secrets.VPS_PORT }}
          VPS_USER: ${{ secrets.VPS_USER }}
        run: ssh -p "$VPS_PORT" "$VPS_USER@$VPS_HOST" "sudo /usr/local/bin/deploy-myapp /tmp/release-${GITHUB_SHA}.tar.gz ${GITHUB_SHA}"

      - name: Verify service
        env:
          VPS_HOST: ${{ secrets.VPS_HOST }}
          VPS_PORT: ${{ secrets.VPS_PORT }}
          VPS_USER: ${{ secrets.VPS_USER }}
        run: ssh -p "$VPS_PORT" "$VPS_USER@$VPS_HOST" 'curl --fail --silent --show-error http://127.0.0.1:8080/health'

The final health request should target an endpoint that checks the process and any dependencies essential for serving traffic. If the application listens on another port or requires HTTPS through a reverse proxy, change the check accordingly. A failed command must return a non-zero status so the workflow is marked unsuccessful.

Adapt the archive to your application

  • For a compiled language, package the binary and required static assets rather than source files and development dependencies.
  • For a frontend or static site, archive the generated output directory and configure the service to point at the active release.
  • For Docker, build and push an image tagged with ${GITHUB_SHA}, pull that exact tag on the VPS, update the service definition, and keep the prior tag available for rollback.
  • For database migrations, run an explicit, backward-compatible migration step before switching traffic. Do not hide destructive schema changes inside an application restart.

Protect production with environments and approvals

Attach the deployment job to a GitHub deployment environment. Environment protection rules can require a named reviewer before production secrets become available. Protect the production branch so a direct, unreviewed push cannot trigger the workflow, and retain a manual workflow_dispatch path for an approved redeployment.

Rank #4
Ledagic IC+RGBW USB Powered Led Light Bar with App & Remote Control, 2 Pack
  • Easy Installation: Our gaming lights have been designed to enhance your entertainment experience. Simply position the led light bar next to your TV or game console to create a game room environment with ease. They come with USB power and a 60 inch cord for direct mounting and mobility
  • Audio Syncing & Extensive Mode: The built-in high-sensitivity music sensor allows you to sync the TV led lights to your favorite music. Lights react and move to the beat of music, film and game audio, creating an incredible immersive sound and light experience – and all without the need for an additional hub. You can choose from 64+ patterns, including strobe, illusion, jump, energy, gradient, running, chasing, fluttering mode etc. Each mode can be applied to different scenes
  • 64+ Chasing IC+RGB & W Effect: The independent control design of IC+RGB and white all-in-one multifunctional rgbw led bar lights monitor backlight allows you to easily switch the led lights for desk between IC+RGB and white to enjoy a variety of lighting effects, you can choose white lighting mode with high brightness for better illumination when working, etc. Choose IC+RGB lighting mode when you need to create an entertaining atmosphere for the light show, 16 million colours of Color changing rgb light bar let you dance to the beat and create a dynamic party atmosphere
  • Dynamic and Dimmable Lights: 1%-full brightness free brightness adjustment gives you the flexibility to adjust dimmable led light bar for room decorations or a variety of occasions
  • USB Powered: USB light bars for room can be powered by powernank, usb charger, TV, Monitor, laptop PC mainframe, making behind TV led lighting bar easy to install in any location.TV led lights are powered by the TV usb interface / PC mainframe, can be turned on/off synchronously with TV / PC

Pin third-party actions to reviewed commit SHAs or use maintained major versions under an update policy. Review changes to workflow files as carefully as application code: a modified workflow can read available secrets or alter the deployment command. Grant the job only the permissions it needs; contents: read is sufficient for the checkout in the example.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Health checks, audit records, and rollback

Health checks

Check the service after activation, not merely the SSH exit status. A useful check verifies an HTTP status, expected response body, and, when appropriate, a dependency such as a database connection. Keep the timeout short enough to fail quickly but long enough for a normal restart. If the check fails, preserve the failed release directory and its logs so the cause can be diagnosed.

Auditability

Record the commit SHA, workflow run, actor, environment, and deployment timestamp. The GitHub Actions run provides the source-side record; the server-side script can append the same release identifier and timestamp to a protected log. Avoid writing private key material or application secrets to that log.

Best Value
Sale
HUANUO FlowLift™ Dual Monitor Stand, Fully Adjustable Gaming Monitor Desk Mount for 13–32″ Computer Screens, Full Motion VESA 75x75/100x100 with C-Clamp & Grommet Base, Each Arm Holds 4.4 to 19.8 lbs
  • Compatible with Wide Screens - To ensure compatibility with the dual monitor mount, your each monitor must meet three conditions at the same time: First, computer screens size range: 13 to 32 inches. Second, screen weight range: 4.4 to 19.8 lbs. Third, the back of the monitor screen must have VESA mounting holes with a pitch of 75x75mm or 100x100mm.
  • Regarding the compatibility with desks - Your desk must meet three conditions at the same time: First, desk material: Only wooden desks are recommended, plastic or glass desks cannot be used. Second, desk thickness range: 0.59" - 3.54". Third, the bottom of the desk should not have any cross beams or panels, as this will interfere with installation. We recommend carefully checking that your desk and monitors meets all above conditions before purchasing.
  • Dual C-Clamp Hold - Worried your dual monitors might wobble or slip? Our upgraded base uses a larger platform plus a dual C-clamp structure to lock the dual monitor arm firmly to your desk. Each arm safely keeps your screens steady while you type, click and game—no shaking, no sliding, just a clean and secure setup you can trust every day. It also provides Grommet Mounting installation choice, both options ensure stable and secure fixation for your 0.59" - 3.54" desk.
  • Full-Motion Adjustment For Comfortable View - Pull the screen closer when you’re deep in a spreadsheet, push it back to watch videos, or rotate to portrait for coding — moving everything smoothly with just one hand. The monitor stand offers +85°/-50° tilt, ±90° swivel and 360° rotation. Raise your monitor up to 15.75″ to support a healthy sitting posture. Whether you’re working from home, gaming through the night, or switching between video calls and documents, getting the screens to your natural line of sight helps relieve neck, shoulder and back strain so you can stay focused longer with less fatigue.
  • Keep Your Desk Organized: By lifting both screens off the desktop, this dual monitor stand opens up valuable space for your keyboard, notebook, docking station or a simple, clutter-free work area. Built-in cable management guides wires along the arms, keeping cords out of sight and out of the way. Enjoy a tidy, modern workstation that looks as good as it feels to use.

Rollback by switching the active release

Because each release has its own directory, rollback does not require rebuilding. List the retained directories, point /srv/myapp/current at the previous known-good one, restart the service, and run the health check again. For containers, stop the current container and start the previous commit-SHA image with the same configuration. Document who can approve a rollback and which database changes are reversible before an incident occurs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set up a self-hosted runner on the VPS

A self-hosted runner is appropriate only when the benefits of local execution outweigh the larger trust boundary. The runner process must remain online, and jobs can access files, credentials, sockets, and network paths available to its account.

  1. Prepare a dedicated, non-root operating-system account and a separately maintained work directory. Do not run the runner as root.
  2. In the repository or organization settings, open Actions, then Runners, choose New self-hosted runner, select the VPS operating system and architecture, and follow the displayed download and registration commands.
  3. Assign a dedicated label such as vps-prod. Restrict the runner to the repository or organization that genuinely needs it.
  4. Install the runner as a service so it starts after reboot, and monitor its process, disk space, CPU, and job logs.
  5. Target it explicitly in the workflow:
jobs:
  deploy:
    runs-on: [self-hosted, linux, x64, vps-prod]
    environment: production
    steps:
      - uses: actions/checkout@v4
      - name: Deploy locally
        run: /usr/local/bin/deploy-from-workspace

Do not assume that the production environment containerizes this job. GitHub states that self-hosted jobs are not isolated in a container merely because an environment is used. Treat every workflow change as code that may execute with the runner’s local permissions. Keep unrelated repositories off the runner, avoid storing long-lived cloud credentials on disk, and remove the runner registration when the machine or repository no longer needs it.

Security and operations checklist

  • Use a dedicated least-privilege deploy account; avoid root SSH keys.
  • Keep private keys, registry credentials, and runtime secrets in GitHub secrets or environment secrets, never in YAML or the repository.
  • Protect the production branch and require an environment approval for production releases.
  • Verify the VPS host key instead of accepting an unknown host key automatically.
  • Pin actions to reviewed commit SHAs or maintained major versions.
  • Build once and deploy an immutable archive or commit-SHA image.
  • Make the remote script idempotent: rerunning it with the same release should produce the same active version without deleting unrelated data.
  • Add a health check that fails the workflow when the service is unhealthy.
  • Retain at least one known-good release and test the rollback procedure.
  • Rotate deploy keys and remove them when a runner, repository, or team member no longer needs access.
  • For self-hosted runners, patch the operating system and runner, restrict labels and repository access, monitor logs, and assume a job can reach the runner’s local trust boundary.

Troubleshoot the common failures

Symptom Likely cause Fix
Permission denied (publickey) The private key, username, file mode, or authorized_keys entry is wrong. Confirm the secret contains the complete key, the workflow writes it with mode 600, the public key is on the intended account, and the SSH username is not root.
Host-key verification failed The stored host key does not match the server, or no trusted key was supplied. Obtain the fingerprint through an independent channel, update the known-hosts secret, and investigate unexpected changes before retrying.
Connection timeout The SSH port is blocked by a firewall, the VPS is down, or the server does not accept the runner’s egress path. Check the VPS firewall and listening port from an approved network. If runner egress addresses are restricted, update the allow-list or use a different architecture.
Archive uploads but the service stays old The symlink was not switched, the service uses a fixed path, or a restart failed. Inspect /srv/myapp/current, the systemd unit’s working directory, and service logs. Run the deployment script manually with the same archive.
Health check fails after a successful restart The process is listening on another address or port, startup is slow, configuration is missing, or a dependency is unavailable. Check the service status and logs, verify the bound port, confirm shared configuration permissions, and make the health check match the actual readiness condition.
Self-hosted jobs remain queued The runner service is stopped, offline, mislabeled, or does not match the runs-on labels. Check runner status in repository settings, restart the service, verify labels and architecture, and confirm the host has capacity.
A rerun behaves differently The workflow pulled a mutable image or rebuilt from moving dependencies. Use the commit SHA as the artifact or image identity, lock dependencies, and retain the original artifact for redeployment.

Which approach should you use?

For a conventional web application, start with a GitHub-hosted runner and SSH. It keeps compilation away from production, limits the VPS integration to a small deployment account, and makes runner maintenance someone else’s responsibility. Choose a self-hosted runner only when direct local access, private-network connectivity, or specialized build capacity is a deliberate requirement. In either model, the dependable pattern is the same: protect the trigger, keep credentials out of source control, build once, deploy an immutable version, verify health, and preserve a tested rollback.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.