# Auto-deploy on push
URL: https://synquic.tech/docs/guides/auto-deploy.md

Make Synq rebuild and ship your app every time you push, from GitHub or from GitLab, Bitbucket, Gitea and Forgejo. Covers webhook setup per provider, signatures, branch matching, the delivery log and troubleshooting.

import { Step, Steps } from 'fumadocs-ui/components/steps';

Once your app is deployed, you don't want to press **Deploy** every time you change something.
**Auto-deploy** does it for you: you push to the project's branch as usual, the Git host tells Synq about
the push through a **webhook** (a small HTTP message sent the moment you push), and Synq builds and ships
that commit.

## Which setup applies

| Where the project's code comes from | How pushes reach Synq | Set it up in |
|---|---|---|
| A repository from the **GitHub** tab | A GitHub webhook Synq creates for you, or your own GitHub App | [GitHub](#github) |
| A repository from the **Git URL** tab (GitLab, Bitbucket, Gitea, Forgejo, Codeberg, other) | A webhook you add in the provider, with a URL and secret Synq gives you | [GitLab, Bitbucket, Gitea and Forgejo](#gitlab-bitbucket-gitea-and-forgejo) |
| A folder, archive, pasted Dockerfile or Compose file, or a Docker image | No repository to push to | Use a [deploy hook](/docs/guides/deploy-hooks) |

In every case the push has to reach Synq over the internet: the panel needs a public address:
`SYNQ_PUBLIC_URL` (for example `https://panel.example.com`, set by the installer's `--domain` or by
`synq panel-domain`), or a verified, SSL-ready
primary domain on the Synq app itself. The GitHub setup has a way around that (a
verified project domain); the others do not.

## GitHub

<Callout title="What you need first">
- A project **linked to a GitHub repository** (see [Deploy from GitHub](/docs/guides/deploy-from-github)).
- A way for GitHub to reach Synq: **Synq on a public web address**, or a **verified domain** on the project
  for direct delivery. Synq tells you which one applies.
</Callout>

<Steps>

<Step>

### Open the project's Source tab

Open your project, then **Source & Triggers → Source**. The **Source Repository** card holds the deploy
triggers for this repository.

</Step>

<Step>

### Turn on Auto Deploy

Switch on the **Auto Deploy** card. When it is on, the card reads **Enabled** and "Pushes trigger
deployments automatically". When it is off, it reads **Disabled** and "Deployments must be started
manually".

From now on, every `git push` to the deploy branch builds and ships automatically.

</Step>

<Step>

### If Synq asks for a webhook endpoint

When Synq cannot be reached from the internet on its own (a laptop, a private network, no public URL), you
see an **Enable auto-deploy** notice: "Auto-deploy needs a direct webhook endpoint. Expose this Synq API on
a public URL or configure a verified webhook domain for direct delivery."

The fix is the **Webhook Endpoint** card. Pick one of the project's **verified domains** from the
**Select domain...** menu, and GitHub delivers push events straight to that domain. To stop using it, open
the same menu and choose **Clear selection**.

<Callout title="Don't see the Webhook Endpoint card?" type="info">
The card appears once the project has at least one **verified** domain. Attach and verify a domain first
(see [Custom domains](/docs/guides/custom-domains)), then come back. The menu only lists this project's
verified domains.
</Callout>

</Step>

<Step>

### Check the status

The **Webhook** card shows whether GitHub is reaching Synq: **Active** ("Repository events are reaching
Synq"), **Direct** (events arrive through your chosen domain), or **Inactive** ("Webhook has not been
configured or is not responding"). After your next push, glance at this card and at the
[delivery log](#delivery-log).

</Step>

</Steps>

### How the GitHub webhook is wired

Synq picks the simplest route that works:

- **Public Synq**: turning on Auto Deploy registers a webhook on the repository that points at
  `<public URL>/api/proxy/api/webhooks/github`, for example
  `https://app.example.com/api/proxy/api/webhooks/github`. Each project gets its own webhook secret, and
  every delivery's `X-Hub-Signature-256` signature is checked against it.
- **Verified domain**: with a **Webhook Endpoint** set, GitHub delivers to
  `https://<your-domain>/_synq/hooks/github`, which the edge passes to Synq. This is the option for
  private installs.
- **Your own GitHub App**: with a [self-hosted GitHub App](/docs/guides/self-hosted-github-app), push events
  arrive through the App; there is no separate per-repository webhook to manage.

Creating the repository webhook needs **admin** access to the repository through your GitHub connection.

### Which GitHub pushes deploy

- Only pushes to the project's **deploy branch** deploy. Pushes to other branches are ignored (preview
  environments, if you use them, track their own branches; see
  [Preview environments](/docs/guides/preview-environments)).
- For a Compose or monorepo project, Synq reads which files changed and rebuilds only the services they
  affect. A push that touches none of them is skipped.
- A commit that is already building or live is not deployed twice, and a redelivered webhook is recognized.

### From the CLI

You need the project's ID (`synq project list`).

```bash
# Turn auto-deploy on, or off again
synq project git auto-deploy <project-id> --enable
synq project git auto-deploy <project-id> --disable

# Deliver webhooks to a verified project domain (for private instances), or stop
synq project git webhook-domain <project-id> deploy.yourapp.com
synq project git webhook-domain <project-id> --clear

# Change the deploy branch
synq project git branch <project-id> main
```

## GitLab, Bitbucket, Gitea and Forgejo

A project imported from the **Git URL** tab (see [Deploy from any Git URL](/docs/guides/deploy-from-git-url))
has its own webhook URL and secret. You turn auto-deploy on in Synq, then add that webhook in your Git
provider once.

<Steps>

<Step>

### Open the project's Source tab

Open the project, then **Source & Triggers → Source**. Below the **Git repository** card is the
**Deploy on push** card.

</Step>

<Step>

### Turn on Auto-deploy on push

Switch it on. The card then says "Pushes to main deploy this project automatically." (with your branch).
While it is off, deliveries are received and logged but nothing deploys.

If you see "This instance has no public URL yet, so a hosted Git provider cannot reach this webhook", give Synq
a public URL first: set `SYNQ_PUBLIC_URL` (see [Installation](/docs/getting-started/installation)) or add a
verified domain to the Synq app itself.
A Git server on your own network can still reach a private address.

</Step>

<Step>

### Copy the webhook URL and secret

The card shows:

- **Webhook URL**, for example
  `https://app.example.com/api/proxy/api/webhooks/git/<project-id>`. One URL per project.
- **Secret** (masked; use the eye button to show it, and the copy button to copy it).

</Step>

<Step>

### Add the webhook in your provider

**Set up the webhook** has a tab per provider with the exact steps. They are repeated below. Use a branch
filter in the provider when it offers one; Synq checks the branch either way.

</Step>

<Step>

### Send a test

Use the provider's test button, or push a commit, and watch **Recent deliveries**. A ping (Bitbucket's
connection test, for example) is logged as `ignored` and answered "Webhook reachable and verified". A test
push (GitLab, Gitea and Forgejo send a sample push of the latest commit) is handled like a real push: it
deploys when it is for the deploy branch, and is `skipped` when that commit is already live. A real push
arrives as `dispatched`.

</Step>

</Steps>

### GitLab

1. In GitLab, open the project, then **Settings → Webhooks → Add new webhook**.
2. Paste the **Webhook URL** into **URL**.
3. Paste the **Secret** into **Secret token**.
4. Under **Trigger**, select **Push events** (you can filter to your deploy branch), then **Add webhook**.

GitLab sends the secret as the `X-Gitlab-Token` header. Works for gitlab.com and self-managed GitLab.

### Gitea

1. In Gitea, open the repository, then **Settings → Webhooks → Add webhook → Gitea**.
2. Paste the **Webhook URL** into **Target URL** and keep **POST** with `application/json`.
3. Paste the **Secret** into **Secret**.
4. Choose **Push events**, set **Branch filter** to your deploy branch, then **Add webhook**.

### Forgejo and Codeberg

1. In Forgejo, open the repository, then **Settings → Webhooks → Add webhook → Forgejo**.
2. Paste the **Webhook URL** into **Target URL** and keep **POST** with `application/json`.
3. Paste the **Secret** into **Secret**.
4. Choose **Push events**, set **Branch filter** to your deploy branch, then **Add webhook**.

### Bitbucket

1. In Bitbucket, open the repository, then **Repository settings → Webhooks → Add webhook**.
2. Enter a title and paste the **Webhook URL** into **URL**.
3. Paste the **Secret** into **Secret**.
4. Under **Triggers**, keep **Repository push**, then **Save**. Pushes to other branches are ignored.

Bitbucket Cloud (`repo:push`) and Bitbucket Data Center (`repo:refs_changed`) are both understood.

### GitHub repositories imported by Git URL

If you imported a GitHub repository through the **Git URL** tab, add a repository webhook on GitHub
yourself: **Settings → Webhooks → Add webhook**, **Payload URL** set to the Webhook URL, **Content type**
`application/json`, **Secret** set to the Secret, and **Just the push event**.

### Git servers without webhooks

A plain Git server (for example one reached only over SSH) has no webhook settings. Create a
[deploy hook](/docs/guides/deploy-hooks) and call it from the repository's `post-receive` hook, for example
`curl -fsS -X POST -H "Authorization: Bearer <token>" <delivery URL>`.

### How deliveries are verified

The secret proves a delivery is for this project. Each provider sends it differently:

| Provider | Header | Value |
|---|---|---|
| GitLab | `X-Gitlab-Token` | The secret itself. |
| Forgejo | `X-Forgejo-Signature` | HMAC-SHA256 of the body, hex. |
| Gitea | `X-Gitea-Signature` | HMAC-SHA256 of the body, hex. |
| GitHub and compatible | `X-Hub-Signature-256` | `sha256=` plus the HMAC-SHA256 of the body, hex. |
| Bitbucket | `X-Hub-Signature` | `sha256=` plus the HMAC-SHA256 of the body, hex. |

A delivery with none of these, or with an old SHA-1 signature, is refused with `401`. The body must be
JSON (content type `application/json`).

### Which pushes deploy

- Only a push to the project's **Branch** deploys. When the branch field is empty, the repository's default
  branch counts. A deleted branch never deploys.
- A provider's ping or test event is answered and logged as `ignored`. Other events (tags, merge requests,
  issues) are `ignored` too.
- With **Auto-deploy on push** off, a push is logged as `skipped` ("Auto-deploy on push is off for this
  project.").
- A commit that is already deployed or deploying is `skipped`, so redeliveries are harmless.
- For a Compose or monorepo project, GitLab, Gitea and Forgejo include the changed files, so only the
  affected services rebuild. Bitbucket does not send file lists, and neither does any provider for a force
  push or a push of 20 commits or more: then every service rebuilds.
- The deploy runs as the organization's owner, since no person pressed the button.

### Rotate the secret

Press **Rotate secret** on the **Deploy on push** card. Deliveries fail with `401` until you paste the new
secret into the provider's webhook settings, so do both at once.

## Delivery log

Every push delivery and deploy hook call is logged per project:

- For Git URL projects, **Recent deliveries** sits under the **Deploy on push** card ("No deliveries yet.
  Use your provider's test button to send one." until the first arrives).
- For every project, **Source & Triggers → Webhooks** shows the same feed under **Recent deliveries**
  ("GitHub pushes and custom hook calls received for this project.").

Each row shows the outcome (`dispatched`, `skipped`, `ignored`, `failed`), the branch and short commit for a
push, when it arrived, and the error for a failed delivery. **Load more** loads older rows.

## Troubleshooting

<Callout title="The GitHub Auto Deploy toggle won't turn on" type="warn">
You see the **Enable auto-deploy** notice, and the error reads "Set a webhook domain or expose this Synq API
on a public URL to enable auto-deploy." GitHub has no way to reach Synq yet. Put Synq on a public address,
or set a **Webhook Endpoint** domain on the Source tab.
</Callout>

<Callout title="“Domain must be verified before it can receive webhooks”" type="error">
The domain you picked is not verified yet. Verify it first ([Custom domains](/docs/guides/custom-domains)),
then select it again.
</Callout>

<Callout title="“Could not create webhook”" type="error">
Synq could not add the webhook to your GitHub repository, usually because your GitHub connection lacks
**admin** access to it. Reconnect GitHub, make sure the repository is included with full access, then
toggle Auto Deploy again.
</Callout>

<Callout title="GitLab, Gitea, Forgejo or Bitbucket shows 401" type="warn">
The secret in the provider does not match the project's secret, or it was not set. The delivery log says
which: "No signature or token header. Set the secret in the provider's webhook settings." or "The signature
or token does not match this project's webhook secret." Copy the **Secret** again (after a rotate, the old
one no longer works).
</Callout>

<Callout title="The provider shows 400" type="warn">
The body was not JSON. In Gitea or Forgejo, set the content type to `application/json`.
</Callout>

<Callout title="The provider shows 404" type="warn">
The URL does not belong to a Git URL project with a webhook secret: check that you copied the whole
**Webhook URL** from this project, and that the project still exists.
</Callout>

<Callout title="I pushed, but nothing deployed" type="warn">
Open the [delivery log](#delivery-log). No row at all means the delivery never reached Synq: check the
public URL, your firewall, and the provider's own delivery history. A `skipped` row says why (another
branch, auto-deploy off, commit already live, no service affected). For GitHub, also check the **Webhook**
card; **Inactive** means delivery is not reaching Synq.

If another deployment is running, a GitHub push is reported as a **failed** delivery, with the reason in the
delivery log. Wait for the running deployment to finish or cancel it, then **Redeliver** the failed event
from GitHub's webhook delivery history (GitHub does not retry on its own). Synq accepts a retry of the same
failed delivery and only retries the projects not yet handled; a successful delivery is never deployed
twice. A Git URL provider that gets a `500` ("The deployment could not be started.") offers the same
redelivery.
</Callout>

## Related

<Cards>
  <Card title="Deploy from any Git URL" href="/docs/guides/deploy-from-git-url" description="Import from GitLab, Bitbucket, Gitea, Forgejo or your own server." />
  <Card title="Deploy hooks" href="/docs/guides/deploy-hooks" description="Deploy from CI, a registry or a script with a URL call." />
  <Card title="Rollback & redeploy" href="/docs/guides/rollback-redeploy" description="Undo a push that went wrong." />
</Cards>
