Run your own backend

Set up CI/CD deploys to your own server

The short version: push-to-deploy is not a PaaS-only feature. A CI pipeline (GitHub Actions or similar) builds and tests, then deploys over SSH to your own server, health-checks the new version, and rolls back if it fails. Keep the deploy key in CI secrets, gate the restart on a passing healthcheck, and keep the previous release ready to swap back - and you get reliable automated deploys on infrastructure you control.

The four stages of a safe pipeline

1. Build & test in CI

Install, build, run the tests. A failed test stops the deploy before it ever touches the server.

2. Deploy over SSH

Connect with a deploy key from CI secrets, ship the new build, and prepare the new release alongside the current one.

3. Health-check

Start the new version and confirm its healthcheck returns 200 before any traffic is shifted to it.

4. Roll back on failure

If health fails, swap back to the previous release automatically - the bad build never serves a request.

A minimal GitHub Actions deploy

name: deploy
on: { push: { branches: [main] } }
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test && npm run build
      - name: ship + restart over SSH
        run: |
          echo "$DEPLOY_KEY" > key && chmod 600 key
          rsync -e "ssh -i key" -az ./dist/ deploy@$HOST:/srv/app/releases/$GITHUB_SHA/
          ssh -i key deploy@$HOST "/srv/app/activate.sh $GITHUB_SHA"
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
          HOST: ${{ secrets.DEPLOY_HOST }}

activate.sh on the server points current at the new release, restarts the service, curls the healthcheck, and - if it fails - re-points current at the previous release and restarts. That one script is your zero-downtime swap and your rollback.

Make it safe

Gate the deploy on health, not hope

The difference between a deploy script and a safe deploy is the gate: the new version only becomes live once it proves healthy, and a failed healthcheck rolls back automatically before users notice. Keep the deploy key in CI secrets and scoped to least privilege, pin the host key, restart gracefully so in-flight requests finish, and keep the last good release one symlink away. Automated does not have to mean reckless.

Generate the pieces: the GitHub Actions deploy workflow, a healthcheck endpoint, and a systemd service with a graceful restart.

How Infraveil handles this

A deploy gate, not just a deploy script

A pipeline that can push to production is also the thing that can break it - which is exactly what Infraveil governs. On your own servers, a deploy runs as an approved, health-gated, recorded action: the new version proves healthy before it takes traffic, a failed check rolls back automatically, and every deploy is written to an audit trail - so push-to-deploy stays fast without becoming the way a bad build silently reaches users.

Deploys health-gated and rolled back automatically on failure, on infra you own
Every deploy recorded - who shipped what, when, and whether it passed
Push-to-deploy speed without an unguarded path to production

Frequently asked questions

Do I need Kubernetes or a PaaS for automated deploys?

No. For one app or a few services, a CI workflow that SSHes in, swaps the release, and restarts is simpler, cheaper, and fully yours. Kubernetes earns its complexity at scale; a PaaS trades control for convenience. Push-to-deploy on your own server is automated but yours.

How do I deploy over SSH safely from CI?

Use a dedicated deploy key in CI secrets (never in the repo), scoped to least privilege, and pin the server host key. The workflow connects, ships the build, restarts, and checks health. Treat the deploy identity like any production credential.

How do I avoid downtime during the deploy?

Start the new version alongside the old, wait for its healthcheck, then shift traffic and retire the old - or use a reverse proxy and graceful restart so in-flight requests finish. The new version goes live only once it proves healthy.

How does rollback work?

Keep the previous release on the server (symlinked current/ + timestamped releases/) so rollback is re-pointing and restarting - seconds, not a rebuild. Best: roll back automatically when the post-deploy healthcheck fails, before the bad build takes traffic.