Skip to content
Ravi Agheda
← All writing

— 2 min read

GitHub Action with EC2 using SSH

Deploy to an EC2 instance over SSH from a GitHub Actions workflow, without exposing long-lived credentials.

  • GitHub
  • AWS
  • SSH

The simplest way to deploy to an EC2 instance from CI is to SSH in and run your deploy commands directly. No build servers, no extra infrastructure — just a workflow step that connects to the box and pulls the latest code.

1. Generate a deploy key

Create a dedicated SSH key pair for CI, rather than reusing your personal key:

ssh-keygen -t ed25519 -C "github-actions-deploy" -f deploy_key -N ""

Add the public key (deploy_key.pub) to ~/.ssh/authorized_keys on the EC2 instance, under whatever user will run the deploy (ec2-user, ubuntu, or a dedicated service account).

2. Store the private key as a GitHub secret

In your repo, go to Settings → Secrets and variables → Actions and add:

  • EC2_SSH_KEY — the private key contents (deploy_key)
  • EC2_HOST — the instance's public IP or DNS name
  • EC2_USER — the SSH user

3. Write the workflow

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy over SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            cd /var/www/app
            git pull origin main
            npm ci
            npm run build
            pm2 restart app

appleboy/ssh-action handles the connection and known-hosts handling for you — no need to hand-roll ssh -i key.pem calls in the workflow itself.

Notes

  • Restrict the EC2 security group's inbound SSH rule to GitHub's published IP ranges if you want to avoid opening port 22 to the world.
  • If the deploy user needs sudo for anything (restarting a system service, writing to /etc), configure passwordless sudo for just that command via visudo, rather than giving CI a fully privileged account.
  • Rotate the deploy key if it's ever exposed — it's cheap to regenerate and doesn't require touching anything else in the pipeline.