# Deploy from any Git URL
URL: https://synquic.tech/docs/guides/deploy-from-git-url.md

Deploy a repository from GitLab, Bitbucket, Gitea, Forgejo, Codeberg or your own Git server, over HTTPS or SSH, with an access token or a deploy key Synq generates. Covers SSH host key pinning, branches, monorepos and changing the source later.

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

The **Git URL** source deploys a repository from any Git host, not only GitHub: GitLab (gitlab.com or
self-managed), Bitbucket (Cloud or Data Center), Gitea, Forgejo, Codeberg, or a plain Git server you run
yourself. You paste the repository URL, tell Synq how to reach it (public, a token, or a deploy key), pick
a branch, and deploy. Synq then clones that repository with the same credential on every deploy, and can
redeploy on every push through the provider's webhook.

<Callout title="GitHub repositories">
You can paste a GitHub URL here too, and it works like any other Git host. The **GitHub** tab is usually
the better choice for GitHub: it lists your repositories, shows recent commits, and manages the push
webhook for you. See [Deploy from GitHub](/docs/guides/deploy-from-github). When you paste an `https://`
GitHub URL with **Public** access, the form offers **Use the GitHub importer** for this reason.
</Callout>

## What you need

- The repository URL. Copy the clone URL from your Git host (the **Clone** or **Code** button), or the
  address of the repository page in your browser.
- For a private repository, one of:
  - an **access token** with read access to the repository, or
  - permission to add a **deploy key** to the repository (Synq generates the key for you).
- For SSH deploy keys: `ssh-keygen` on the Synq host. A standard install has it; if key generation fails
  with "ssh-keygen is not available on this host", install the `openssh-client` package on the server.

## Which URLs work

| You paste | Accepted | Notes |
|---|---|---|
| `https://gitlab.com/group/project.git` | Yes | HTTPS. Works with Public or Access token. |
| `https://gitlab.com/group/subgroup/project` | Yes | Nested groups are kept. `.git` is optional. |
| `https://gitlab.com/group/project/-/tree/main` | Yes | The browser part (`/-/tree/main`) is dropped. |
| `git@gitlab.com:group/project.git` | Yes | SSH (SCP style). Needs a deploy key. |
| `ssh://git@git.example.com:2222/team/app.git` | Yes | SSH on a custom port. Needs a deploy key. |
| `https://user@bitbucket.org/team/app.git` | Yes | In the panel, also type the username into **Username (optional)**: an empty field replaces the URL's username. |
| `https://user:secret@host/app.git` | No | Never put a password or token in the URL. Use the token field. |
| `http://...`, `git://...`, `file://...` | No | Plain HTTP would send a token unencrypted; `git://` and `file://` are not allowed. |

Synq recognizes the provider from the host name: `gitlab.com`, `bitbucket.org`, `gitea.com`, `codeberg.org`
(Forgejo), and any host with a name part that is `gitlab`, `bitbucket`, `gitea` or `forgejo`, or starts with one of
them plus a dash (for example `gitlab.example.com` or `gitlab-eu.corp.com`, but not `mygitlab.example.com`). Any other host is shown as a plain **Git** repository. The provider only changes the
icon, the hints and the default webhook instructions; every host is cloned the same way.

## Deploy a repository

<Steps>

<Step>

### Open the Git URL tab

In the panel, click **New Project** in the sidebar, then the **Git URL** tab. The card is titled
**Deploy from a Git URL**. (You can also open `/library#git` directly.)

</Step>

<Step>

### Paste the repository URL

Paste it into **Repository URL**. Under the field Synq shows what it detected, for example
"GitLab repository gitlab.com/acme/shop". If it says "Enter an https:// or SSH Git URL", the URL is not in
one of the accepted forms above.

</Step>

<Step>

### Choose how Synq reaches it

Under **Access**, pick one:

- **Public**: no credential. Anyone can read the repository. HTTPS URLs only.
- **Access token**: a personal, project or deploy token, stored encrypted. HTTPS URLs only. Paste it into
  **Access token**, and fill **Username (optional)** only when your token type needs one (see
  [Access tokens by provider](#access-tokens-by-provider)).
- **Deploy key**: press **Generate deploy key**. Synq creates an SSH key pair for this project and shows
  the **Public key**. Copy it and add it to the repository with **read** access. The form tells you where,
  for example "On GitLab: Settings, Repository, Deploy keys." Works with SSH URLs (and with an HTTPS URL
  of the same repository: Synq clones it over SSH).

</Step>

<Step>

### Press Test access

Synq runs `git ls-remote` against the repository with the credential you chose. On success you see
"Access works. Branches found: N." For a deploy key you also see "Server key pinned:" followed by the SSH
server's key type and fingerprint (see [SSH host key pinning](#ssh-host-key-pinning)).

If it fails, the message says why. See [Test access errors](#test-access-errors).

</Step>

<Step>

### Pick the branch, name and folder

- **Branch**: the branch to deploy. It starts on the repository's default branch.
- **Project name**: taken from the repository name; change it if you like.
- **Root directory (optional)**: for a monorepo, the folder that holds the app, for example `apps/web`.
  Leave it empty to use the repository root.

Press **Continue**. Synq shallow-clones that branch ("Fetching the branch…") and opens the
[deploy screen](/docs/dashboard/deploy-wizard).

</Step>

<Step>

### Check the detected build and press Deploy

The deploy screen scans the fetched code exactly like an uploaded folder: framework, install, build and
start commands, the port, or a Dockerfile or Compose file. Detection follows the
[Dockerfile-first rule](/docs/guides/build-detection#how-synq-decides-what-to-build). Review it, add
environment variables and a domain, and press **Deploy**.

</Step>

</Steps>

From now on every deploy (a manual **Redeploy**, a push webhook, a deploy hook) clones the saved branch
with the saved credential.

## Access tokens by provider

The token needs read access to the repository's code. The **Username** field is only for token types that
authenticate with a specific user name; leave it empty otherwise.

| Provider | Token to create | Username |
|---|---|---|
| GitLab | A personal, group or project access token with the `read_repository` scope. | Leave empty. |
| GitLab | A deploy token with the `read_repository` scope. | The token's generated username (for example `gitlab+deploy-token-12`). |
| Bitbucket Cloud | A repository, project or workspace access token with read access to repositories. | Leave empty. Synq uses `x-token-auth`. |
| Bitbucket Cloud | An app password with repository read permission. | Your Bitbucket username. |
| Gitea, Forgejo, Codeberg | An access token with read access to repositories. | Leave empty. |
| Other Git servers | Whatever your server accepts over HTTPS Basic authentication. | As your server requires. |

The token is stored encrypted and is never shown again. It is only ever sent to this repository's host:
Synq never uses your GitHub sign-in, GitHub App or GitHub tokens for a Git URL project.

## SSH host key pinning

With a deploy key, Synq also has to trust the Git server itself. It does this the first time it connects:

1. On the first **Test access**, Synq reads the server's SSH host keys (`ssh-keyscan`) and **pins** them to
   the project. The success message shows the pinned key, for example
   `Server key pinned: ssh-ed25519 SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8`.
2. Every later clone and check runs with strict host key checking against those pinned keys.
3. If the server ever presents a different key, the connection is refused with **HOST_KEY_MISMATCH**
   ("The server's SSH host key changed or is not trusted yet.") instead of silently trusting it.

Compare the pinned fingerprint with the one your provider publishes (GitLab, Bitbucket and Codeberg list
their SSH host key fingerprints in their documentation). For your own server, run `ssh-keygen -lf` on its
host key file and compare.

When a server's key changes for a legitimate reason (a reinstall, a new host key), open the project's
**Source & Triggers → Source**, find **Pinned SSH host keys**, and press **Trust the server's current key**.
Synq forgets the old keys and pins whatever the server presents on the next connection, so only do this
when you know why the key changed.

## Test access errors

| Code | Message | What to do |
|---|---|---|
| `AUTH_FAILED` | The repository refused the credential. | Check the token and its scope, the username for token types that need one, or that the deploy key was added to this repository. A **Public** repository that answers with an auth error is private: pick a token or deploy key. |
| `NOT_FOUND` | The repository was not found. | Check the URL. A private repository often answers "not found" to a credential that cannot see it. |
| `HOST_KEY_MISMATCH` | The server's SSH host key changed or is not trusted yet. | See [SSH host key pinning](#ssh-host-key-pinning). |
| `UNREACHABLE` | The Git server could not be reached. | The Synq host cannot connect to that host and port: check DNS, the firewall, a VPN, or the SSH port in the URL. Checks give up after 30 seconds. |
| `INVALID_URL` | The repository URL is not valid. | Use one of the [accepted forms](#which-urls-work). An access token or Public access needs an `https://` URL. |
| `NO_BRANCHES` | The repository has no branches yet. | Push a first commit, then test again. |
| `FAILED` | Git could not read the repository. | Read the detail under the message: it is git's own error. |

## Change the source later

Open the project, then **Source & Triggers → Source**. The **Git repository** card shows the repository
and lets you change:

- **Repository URL** and **Branch**. After a successful **Test access**, the branch field becomes a list of
  the repository's branches.
- **Access**: **Public**, **Token** or **Deploy key**. A saved token is never shown; type in **Access token**
  to replace it ("A token is saved. Type to replace it."). Switching to **Deploy key** generates a key when
  you press **Save** ("Save to generate the deploy key for this project.").
- **New key**: generates a fresh deploy key. The old key stops working for Synq, so add the new public key
  to the repository right away.

Press **Save**, then **Test access** (it is disabled until you save: "Save your changes before testing
access."). **Open repository** opens the repository in your browser.

The same card holds the push webhook. See
[Auto-deploy for GitLab, Bitbucket, Gitea and Forgejo](/docs/guides/auto-deploy#gitlab-bitbucket-gitea-and-forgejo).

## Limits

- Every deploy makes a fresh shallow clone of the branch. The import's **Fetching the branch** step is
  stopped after 10 minutes; deploy clones have no such limit.
- The branch list shows at most 500 branches.
- Git URL projects have no **Recent Commits** list and no "This repo has a Dockerfile" suggestion on the
  **Builder** card; both read the repository through the GitHub API. You can still choose any
  [builder](/docs/guides/build-detection#choose-a-builder) by hand.
- The import runs on the Synq host, and deploy clones can run on the build server. Both need outbound
  access to your Git server (HTTPS or SSH).

## API

The panel uses these routes; an API token or the MCP server can use them too. See
[Projects API](/docs/api/projects) for the full reference.

| Route | What it does |
|---|---|
| `POST /api/projects/git/session` | Start an import: `url`, `authMode` (`none`, `token`, `ssh`), and `token`/`username` for a token. For `ssh` it returns the `publicKey`. |
| `POST /api/projects/git/session/:sessionId/check` | Test access; returns the branches and pinned host keys, or a code such as `AUTH_FAILED`. |
| `POST /api/projects/git/session/:sessionId/fetch` | Shallow-clone a `branch` (and optional `rootDirectory`) into the session. |
| `GET` / `PUT /api/projects/:id/git-source` | Read or change a project's URL, branch and access method. `resetHostKey: true` forgets pinned host keys. |
| `POST /api/projects/:id/git-source/check` | Test access with the saved credential. |
| `POST /api/projects/:id/git-source/deploy-key` | Generate a new deploy key. |

After `fetch`, the import continues like a folder upload: scan the session, create the project with
`gitSessionId`, then start the build.

## Related

<Cards>
  <Card title="Auto-deploy on push" href="/docs/guides/auto-deploy" description="Add the webhook to GitLab, Bitbucket, Gitea or Forgejo so every push deploys." />
  <Card title="Build detection and builders" href="/docs/guides/build-detection" description="How Synq decides what to build, and how to pick Dockerfile, Nixpacks, Railpack or Buildpacks." />
  <Card title="Deploy hooks" href="/docs/guides/deploy-hooks" description="Trigger a deploy from CI or any tool with a URL." />
</Cards>
