CI/CD: Deploying with Neo from GitHub Actions
Neo needs SSH access and a license key — that's the whole CI setup. No server-side webhook receiver, no agent to install.
What CI needs
Neo deploys the same way from CI as from your laptop: SSH access to the server, and an activated license. Nothing runs on the server waiting for a webhook — the CI job connects out to the server, the same direction a developer's laptop would.
Secrets to configure
In your repository's GitHub Actions secrets:
NEO_SSH_PRIVATE_KEY— a deploy key with access to the target server (see Team SSH Key Management for adding it there)NEO_ENV_KEY— if the project uses encrypted env files, the decryption key, so the workflow never prompts
A minimal workflow
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Neo
run: curl -fsSL https://neo.vxero.dev/neo | sh
- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.NEO_SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan your-server.example.com >> ~/.ssh/known_hosts
- name: Deploy
env:
NEO_DEV_PLUS: "true" # skip interactive license activation in CI
NEO_ENV_KEY: ${{ secrets.NEO_ENV_KEY }}
run: neo deploy --to production
Why the commit tracking matters here specifically
Since deploy records the git commit that produced each build, a CI-triggered deploy is exactly where that pays off — the commit captured is the exact one GitHub Actions checked out, not an approximation from a developer's local branch state days later:
neo status my-app --json | jq -r .deployment.commit
This should always equal $GITHUB_SHA for the deploy that just ran; a CI job can assert it as a post-deploy check.
Shallow checkouts
GitHub Actions' default checkout is often shallow, which can leave git unable to fully describe the commit locally. Neo falls back to GITHUB_SHA automatically in that case — no extra configuration needed for the commit to still be captured correctly.
Deploying on tag push instead of every commit to main
on:
push:
tags: ['v*']
Pairs naturally with neo status's version display, which shows the git tag (v1.4.2) when the deployed commit is tagged — so neo list reads as your actual release version, not just a commit hash.
Try it yourself — Neo is free, with every feature unlocked.
Get started free