ARTICLE

Auto-Deploy to DMIT with GitHub Actions + rsync

Automate deploys to DMIT with GitHub Actions: secrets, least-privilege sudo, atomic releases and rollback.

Why automate deployment

The manual flow — build locally, scp the archive, SSH in, unzip, restart — is slow, error-prone and untraceable. Handing deployment to CI/CD (such as GitHub Actions) means every push automatically runs tests, builds, uploads and restarts, with full logs and easy rollback. This article demonstrates auto-deploying an app to a DMIT cloud server using GitHub Actions + rsync.

Choosing an approach

ApproachFitsTraits
rsync + SSHMost web appsSimple, incremental transfer, no extra server components
Git pull + hookSmall projects, internal toolsServer pulls code; keys live on the server
Docker image + ComposeContainerised appsConsistent environments; pairs with Docker Deployment

Setup: dedicated deploy user and key

Never use root from CI. Create a restricted deploy user:

# on the server
adduser --disabled-password deploy
mkdir -p /home/deploy/.ssh
# write the public key into authorized_keys (see below)
chmod 700 /home/deploy/.ssh
chown -R deploy:deploy /home/deploy/.ssh

# app directory ownership
chown -R deploy:deploy /var/www/myapp

In GitHub → Settings → Secrets add: SSH_PRIVATE_KEY, SSH_HOST, SSH_USER. The private key exists only as a CI secret, never in the repo.

GitHub Actions workflow

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: npm ci && npm run build

      - name: Setup SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts

      - name: Rsync to server
        run: rsync -avz --delete ./dist/ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/var/www/myapp/

      - name: Restart service
        run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} 'sudo systemctl restart myapp'

ssh-keyscan records the host fingerprint on first connect; for production, pin the fingerprint in known_hosts to prevent MITM.

Security and rollback

  • Least-privilege sudo: allow deploy to run only one command:
    # /etc/sudoers.d/deploy
    deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myapp
  • Atomic releases: rsync into /var/www/myapp-releases/<timestamp>/, flip the current symlink — one command rolls back
  • Keep the last 5 releases, prune older ones
  • Rotate keys periodically; if leaked, deleting the public key on the server revokes access instantly

Common issues

Permission denied (publickey)?

Check server-side ~/.ssh permissions (700/600), authorized_keys ownership, and that the Actions secret contains the full key including header/footer lines.

rsync hangs or times out?

Confirm the firewall allows port 22 from CI runners (their IPs are not fixed), and review your baseline in the Security Checklist.

Deployed but service unchanged?

Verify the restart step targets the right service name; confirm via systemctl status myapp and journalctl -u myapp -f (more in Troubleshooting Guide).

Related articles

Docker Deployment Guide, Security Checklist, First Deploy Checklist.

Data checked: 2026-08-18. Actions syntax follows official GitHub documentation.

NEXT STEP

Ready to pick your DMIT plan?

Filter the official snapshot by budget and network, or check live stock at DMIT.

Disclosure: purchases through this link may earn the referrer a commission at no extra cost to you.