Skip to content

Deploying from GitHub Actions ​

The repository is also a GitHub Action. It runs the node-scp CLI, so it works with SFTP servers and SCP only devices alike.

yaml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: npm ci && npm run build
      - uses: maitrungduc1410/node-scp-async@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: deploy
          private-key: ${{ secrets.DEPLOY_KEY }}
          fingerprint: ${{ secrets.DEPLOY_HOST_FINGERPRINT }}
          source: dist/
          target: /var/www/app
          exclude: |
            *.map
            .DS_Store

Inputs ​

InputDefaultDescription
hostrequiredHost name or IP address. IPv6 works too.
port22SSH port.
usernamerequiredUser name.
private-keyPrivate key contents. Use a secret.
passphrasePassphrase for the key.
passwordPassword, if you cannot use a key.
fingerprintExpected host key, SHA256:.... Comma separated for several.
sourcerequiredPaths to copy, one per line.
targetrequiredDestination.
directionuploadupload or download. For downloads source is remote and target local.
protocolautoauto, sftp or scp.
recursivetrueCopy directories.
preservefalseKeep times and full modes. New files get the source permissions either way.
excludePatterns to skip, one per line. A pattern without / matches names at any depth.
concurrency4Parallel files over SFTP.
version1node-scp version the action runs.

Where files end up ​

The action follows scp rules:

  • source: dist and target: /var/www/app makes /var/www/app a copy of dist when /var/www/app does not exist yet, and creates /var/www/app/dist when it does.
  • A trailing slash on the target (/var/www/app/) always means "into this directory".
  • Several sources always go into the target directory.

To replace a directory atomically, upload to a new release directory and switch a symlink afterwards with an ssh step.

Pin the host key ​

Without fingerprint the action accepts any host key and prints a warning. Get the value once from a trusted network:

sh
ssh-keyscan -t ed25519 example.com 2>/dev/null | ssh-keygen -lf -
# 256 SHA256:q7Jx0fT3cM2yKpV9LrWn4sEaB8uHdZ1oGiXe6NcRtYk example.com (ED25519)

Store SHA256:q7Jx... as a secret or variable and pass it as fingerprint.

Using the CLI directly ​

The action is a thin wrapper, so any step with Node can do the same:

yaml
- run: npx node-scp@1 -r --fingerprint "$FP" dist/ deploy@example.com:/var/www/app
  env:
    NODE_SCP_PRIVATE_KEY: ${{ secrets.DEPLOY_KEY }}
    FP: ${{ vars.DEPLOY_HOST_FINGERPRINT }}

Released under the MIT License.