Deploy from GitHub Actions and other CI
If your tests run in a continuous integration (CI) system such as GitHub Actions, GitLab CI or Bitbucket Pipelines, the natural next step is to deploy only when they pass. SrvYard gives you two ways to start a deployment from there: the deploy hook, a secret address you request with one curl command, and the API, which needs an access token but also lets you wait for the deployment to finish and read its result.
This article shows both, with a complete GitHub Actions workflow for each, explains what the answers mean, how to see in SrvYard what CI did, and how to rotate the secret if it leaks. Whatever you choose, SrvYard deploys the branch configured on the site: CI says when, not what.
Before you begin
- A site that deploys by hand first. Run Deploy now once and read the log (see Write and test a deploy script).
- A secret store in your CI system. The hook address and the API token are credentials. Keep them in the pipeline's secrets, never in the repository.
- For the API route, a token with the right scope. Create it as shown in Create an API token and pick scopes: the scope for starting and reading deployments is
sites:manage-deploys(plussites:viewto read them). - Do not combine with push to deploy unless you want both. If push to deploy is on, every push already deploys; the CI step would add a second deployment.
1. Use the deploy hook
Open the site's Deployments tab. The Deploy hook card holds the address.
- The address. It has the form
https://your-srvyard-host/hooks/deploy/<token>. The card says "Request this URL (GET or POST) from CI or a script to start a deployment. Add?branch=mainto ignore other branches." Only owners and admins see it. - Copy. Copies the address for pasting into your CI secrets.
- Create a new URL. Replaces the token (step 4).
The same address is on Settings > Deployments, under Deploy hook URL, with a Regenerate URL button.
Request it with either method:
curl -fsS "$DEPLOY_HOOK_URL?branch=main"
The answers, always JSON:
| Status | Body | Meaning |
|---|---|---|
| 202 | {"status":"queued","deployment":"<id>"} |
A deployment was queued. If one was already waiting, its id is returned instead and no second one is made. |
| 202 | {"status":"ignored","reason":"The branch does not match the site's branch."} |
You passed ?branch= and it is not the site's branch. Nothing was queued. This is not an error. |
| 404 | {"message":"Not found."} |
The token is wrong or was replaced. |
| 409 | {"status":"rejected","message":"..."} |
The site cannot deploy now: it is not ready, its server is not ready, it has no repository or its deploy script is empty. |
| 429 | More than 30 requests in a minute from one address. |
curl -f turns 4xx and 5xx answers into a non-zero exit status, so a wrong token fails the pipeline step. The hook returns as soon as the deployment is queued; it does not wait for the result. If CI must know whether the deployment worked, use the API (step 3).
The hook takes only an optional branch. It does not choose a commit or another branch: the deployment fetches the tip of the site's branch when it starts. See Deploy now, redeploy and switch branches.
A GitHub Actions workflow
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: composer install --no-interaction --prefer-dist
- run: php artisan test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Start the SrvYard deployment
run: curl -fsS "${{ secrets.SRVYARD_DEPLOY_HOOK }}?branch=main"
Add SRVYARD_DEPLOY_HOOK under Settings > Secrets and variables > Actions of the repository. GitHub hides the value in logs.
2. Check what CI did
Every deployment records how it started.
- A row. In the history, the Trigger column says Deploy hook for the address above and API for a request with a token. The Trigger filter above the table (its entries are named Hook and Api) lists only those, as in the screenshot. The commit, branch and author are the ones SrvYard fetched when the deployment started.
- Triggered by. API or Deploy hook. For the API the audit trail also records the token's name, so you can tell which pipeline started it.
3. Use the API and wait for the result
The API needs a token but answers properly: it returns the deployment, and you can poll it until it finishes.
- Start the deployment (the site id is the UUID in the site's address):
curl -fsS -X POST "https://your-srvyard-host/api/v1/sites/$SITE_ID/deployments" \
-H "Authorization: Bearer $SRVYARD_TOKEN" \
-H "Idempotency-Key: $GITHUB_SHA"
The answer is 202 Accepted with the deployment under data (its id, status, trigger and the commit once known) and a status_url. An Idempotency-Key makes a repeated request with the same key and body return the first answer instead of queueing another deployment.
- Poll until it ends. The statuses are
queued,running,finished,failedandcancelled:
until status=$(curl -fsS "https://your-srvyard-host/api/v1/deployments/$ID" \
-H "Authorization: Bearer $SRVYARD_TOKEN" | jq -r '.data.status'); \
[ "$status" = finished ] || [ "$status" = failed ] || [ "$status" = cancelled ]; do
sleep 10
done
[ "$status" = finished ]
The last line makes the script exit with an error unless the deployment finished. A deployment whose health check fails ends as failed although the release is live.
- Read the log with
GET /api/v1/deployments/<id>/logwhen it failed, and print it in CI so nobody has to open SrvYard.
A workflow that starts the deployment, waits and fails the job on a bad result:
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Deploy and wait
env:
SRVYARD_TOKEN: ${{ secrets.SRVYARD_TOKEN }}
SITE_ID: ${{ vars.SRVYARD_SITE_ID }}
run: |
ID=$(curl -fsS -X POST "https://your-srvyard-host/api/v1/sites/$SITE_ID/deployments" \
-H "Authorization: Bearer $SRVYARD_TOKEN" | jq -r '.data.id')
until status=$(curl -fsS "https://your-srvyard-host/api/v1/deployments/$ID" \
-H "Authorization: Bearer $SRVYARD_TOKEN" | jq -r '.data.status'); \
[ "$status" = finished ] || [ "$status" = failed ] || [ "$status" = cancelled ]; do
sleep 10
done
echo "Deployment $ID: $status"
[ "$status" = finished ]
The API allows 60 requests a minute per token. Polling every ten seconds stays far below that. The conventions (errors, pagination, rate limit headers) are in Conventions: authentication, pagination, errors, rate limits.
4. Rotate the secret
If the hook address was ever pasted somewhere it should not be (a log, a ticket, a public repository), replace it.
- Create new URL. The current address stops working immediately and requests to it answer 404. Update the secret in every pipeline that uses it. The new address is shown on the card.
For an API token, delete it and create a new one under your account's API tokens. See Rotate tokens and review the sign-in log.
Troubleshooting
- The step fails with
curl: (22) ... 404. The address is wrong or was regenerated. Copy the current one from the Deploy hook card. - The step succeeds but nothing deploys. The answer was
202with"status":"ignored": your?branch=differs from the site's branch. Fix the branch in the URL or in the site's repository settings. 409 rejected. Read the message: the deploy script may be empty, the repository missing or the server not ready.- Two deployments for one push. Push to deploy and the CI step are both on. Turn one off.
- The workflow finishes before the deployment does. The hook only queues the deployment. Use the API and poll, as above.
403 missing_scope. The token lackssites:manage-deploys. Create a new token with the scope.429. Too many requests. Wait for the time in theRetry-Afterheader.
Related
- Deploy automatically on every push: no CI needed.
- Create an API token and pick scopes: the token for step 3.
- List servers and sites, trigger actions: more endpoints.
- Read a deployment log and find the failing step: when CI reports a failure.