CodeKits.devNew developer offer

Deploy to Ubuntu with GitHub Actions

A practical walkthrough of deploy to ubuntu with github actions, with code excerpts, failure checks and integration guidance.

By Jonathan · 5 min read

Published

Deploy to Ubuntu with GitHub Actions tutorial cover
In this article

This walkthrough covers Deploy to Ubuntu with GitHub Actions. A repository with meaningful tests, a staging Ubuntu server, SSH access and a health endpoint. Adapt service names and release paths to your own application.

The video is an illustrated code walkthrough with AI narration. Code blocks are integration excerpts, not a complete tested application. Helper names such as stores, middleware and clients describe responsibilities you must implement in your existing project. No live server, payment provider or device execution is claimed.

Before you begin

Use a separate development environment, fictional data and test credentials. Record dependency versions and follow the official references below for your installed versions. Keep a working baseline and add one workflow at a time.

1. Separate checks from deployment

Design deployment as a sequence with explicit failure points. Pull requests should run checks without receiving production credentials. A protected branch can trigger a deployment environment with appropriate controls. Build a versioned release, activate it, and verify health. A rollback plan belongs in the first version of the workflow, not as an improvised command after an outage.

Separate checks from deployment
push / pull request -> tests + build
protected main -> deployment environment
release directory -> activate -> health check
failed health -> restore previous release

2. Use narrow workflow permissions

Grant the workflow only the repository permissions it needs. Serialize production deployments so two releases don't race to replace the active version. Don't interrupt an in-progress deployment at an arbitrary filesystem step unless the process is explicitly cancellation-safe. Restrict who can change workflow files and deployment scripts, and pin reviewed third-party actions to immutable commit identifiers in the real workflow.

Use narrow workflow permissions
permissions:
  contents: read
concurrency:
  group: production-deploy
  cancel-in-progress: false

3. Run reproducible checks

Install from the committed lockfile and run the project's actual tests before building. Record which commit produced the artifact and verify its checksum when transferred. Don't silently rebuild a different dependency set on the server. Native dependencies and runtime compatibility matter, so build in an environment appropriate to the target or use a controlled server-side build of the exact reviewed revision.

Run reproducible checks
npm ci
npm test
npm run build
# Record commit SHA and artifact checksum.

4. Verify the SSH destination

Use a dedicated deployment identity with the least required access. Provision the server's verified host key rather than trusting a new fingerprint automatically during each run. Store credentials in the intended environment's secret facility, and avoid printing them in logs. Where supported, prefer short-lived credentials. Test access against staging before granting the workflow any production capability.

Verify the SSH destination
ssh -o StrictHostKeyChecking=yes deploy@YOUR_HOST
# Provision a verified known_hosts entry beforehand.
# Use a dedicated deployment identity, not a personal key.

5. Create an immutable release

Keep each release in its own directory and separate mutable data from application files. Upload into a new inactive directory, verify the artifact, then prepare dependencies and configuration links. Never overwrite the active tree incrementally while requests are being served. Retain the previous working release until the new one passes health checks and the rollback window has closed.

Create an immutable release
/srv/app/releases/<commit-sha>/
/srv/app/current -> releases/<active-sha>
/srv/app/shared/ -> runtime configuration and durable files

6. Activate and verify

On the Linux target, switch the prepared symlink into place and restart the appropriate service. Poll health with a bounded timeout and validate a response that reflects the application, not merely an open socket. If the check fails, restore the previous target and restart again, then verify recovery. This excerpt shows the activation concept; the full operational script needs error handling and deployment serialization.

Activate and verify
previous=$(readlink /srv/app/current)
ln -s "$release" /srv/app/current.next
mv -Tf /srv/app/current.next /srv/app/current
sudo systemctl restart myapp
# Poll a bounded health check; restore previous on failure.

7. Plan database compatibility

Application rollback doesn't automatically reverse database changes. Prefer compatible expand-and-contract migrations so the previous release can still run during the rollback window. Back up before risky changes and test restoration independently. Record deployment results and notify the responsible operator on failure. Avoid automatic destructive migration rollback unless its data consequences are fully understood and tested.

Plan database compatibility
Release A -> expand schema -> release B
Verify and migrate data
Remove old schema only after rollback window
Backups and restore tests remain separate requirements

Integration checks

Use staging to inject test failure, transfer failure, bad host key, failed startup and failed health. Confirm the previous release recovers and database compatibility is preserved.

These are verification instructions, not reported test results. The excerpts have not been executed as a complete integrated application. Review authentication, database schema, dependency versions and operational settings before deployment.

Official references

Explore CodeKits development services.