Github actions - how to deploy to remote server using SSH

Viewed 29524

I've a staging server on DO.

I want to build & deploy my node app to it.

name: Build & Deploy
on:
  push:
    tags:
      - 'v1.*.0'
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Create SSH key
        run: |
          mkdir -p ~/.ssh/
          echo "$DO_GITHUB_PRIVATE_KEY" > ../github_do.key
          sudo chmod 600 ../github_do.key
          ssh-keyscan -H ${{secrets.DEPLOY_SERVER}} > ~/.ssh/known_hosts
        shell: bash
        env:
          DO_GITHUB_PRIVATE_KEY: ${{secrets.DO_GITHUB_PRIVATE_KEY}}
      - uses: actions/setup-node@v1
        with:
          node-version: 12.x
      - name: Install Packages
        run: yarn install --frozen-lockfile
      - name: Build artifacts
        env:
          DEPLOY_SSH_KEY_PATH: ${{ github.workspace }}/../github_do.key
        run: |
          yarn shipit production fast-deploy

What i've done is to generate a new SSH private & public keys.

The private key I've saved inside DO_GITHUB_PRIVATE_KEY github secret.

The public key I've added to authorized_keys on my staging server.

When the action is triggered, it fails on:

@ v***.256.0
Create release path "/home/***/***/releases/2020-03-0***-v***.256.0"
Running "mkdir -p /home/***/***/releases/2020-03-0***-v***.256.0" on host "***".
@***-err ***@***: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
'fast-deploy:updateRemote' errored after ***.32 s
Error: Command failed: ssh -i /home/runner/work/***/***/../github_do.key ***@*** "mkdir -p /home/***/***/releases/2020-03-0***-v***.256.0"
3 Answers

I've solved it! Apparently keys were protected with passphrase .

This is the whole process:

  1. Genereate new keys

ssh-keygen -t rsa -b 4096 -C "user@host" -q -N ""

  1. Update your host's authorized_keys

    ssh-copy-id -i ~/.ssh/id_rsa.pub user@host

  2. Enter the server & run

ssh-keyscan host

  1. Copy the output to github secret (lets call it SSH_KNOWN_HOSTS)
  2. Copy the private key to a github secret (lets call it SSH_PRIVATE_KEY)

In your workflow.yml file

#workflow.yaml
...
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Create SSH key
        run: |
          mkdir -p ~/.ssh/
          echo "$SSH_PRIVATE_KEY" > ../private.key
          sudo chmod 600 ../private.key
          echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
        shell: bash
        env:
          SSH_PRIVATE_KEY: ${{secrets.SSH_PRIVATE_KEY}}
          SSH_KNOWN_HOSTS: ${{secrets.SSH_KNOWN_HOSTS}}
          SSH_KEY_PATH: ${{ github.workspace }}/../private.key
 

Then you can use ssh with ssh -i $SSH_KEY_PATH user@host

Hope this will save few hours to someone :]

Edit

Answer to comments (how to update github secrets)

In order add github secrets you have 2 options:

  1. Via GitHub ui, https://github.com/{user}/{repo}/settings/secrets/
  2. Via GitHub API, I'm using github-secret-dotenv lib to sync my secrets with my local .env file (pre action trigger)

felixmosh's answer was useful but I managed to simplify it further using id_rsa that will be automatically used by ssh and the secrets will be substituted without needing intermediary env vars:

  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v2
        with:
          name: build
          path: build
      - name: Create SSH key
        run: |
          install -m 600 -D /dev/null ~/.ssh/id_rsa
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
      - name: Deploy with rsync
        run: rsync -rav --delete build/ user@host:/

The answers of felixmosh and Cas but this feels like a better implementation. Rather than uploading your known_hosts file to the Github hosted runners just populate the ~/.ssh/known_hosts file on runtime. More felxible as it might handle issues like IP changing. I tested it and it worked for me.

- name: Write SSH keys
  run: |
    install -m 600 -D /dev/null ~/.ssh/id_rsa
    echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
    ssh-keyscan -H host.example.com > ~/.ssh/known_hosts

Or, even better,

- name: Write SSH keys
  run: |
    install -m 600 -D /dev/null ~/.ssh/id_rsa
    echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
    host='host.example.com'
    hosts="$(dig +short "$host" | grep -v '\.$' | sed -z 's|\n|,|g')$host"
    ssh-keyscan -H "$hosts" > ~/.ssh/known_hosts

This ensures that all ips of the host are recorded in known_hosts.

Just replace host.example.com and you are good to go.

Related