---
title: Git Sources
description: Link a stack to a Git repository and keep compose.yaml in sync via manual pulls or webhook triggers.
---

Git Sources turn any stack into a GitOps target. Point Sencho at a repository, branch, and `compose.yaml` path; pull updates on demand or from CI; and review a diff before applying changes to disk. Optional sibling `.env` sync keeps configuration consistent too.

Git Sources are available to all Sencho users on the Community tier.

## How it works

1. Open a stack's editor and click **Git Source**.
2. Fill in the repository URL, branch, and compose file path. Add a token if the repo is private.
3. Click **Pull now** to fetch the latest compose content. Sencho shows a side-by-side diff.
4. Click **Apply** to write the incoming content to disk. Optionally deploy immediately.

Writes land in the stack's existing directory using the same storage Sencho uses for the in-browser editor. Existing history, alerts, and metrics are unaffected.

## Create a stack from a Git repository

Skip the "empty stack then link later" detour and point at a repo from the start. Click **Create Stack** in the sidebar, switch to the **From Git** tab, and fill in the same fields you would on the Git Source panel.

<Frame>
  <img src="/images/git-sources/create-from-git-tab.png" alt="Create Stack dialog with the From Git tab selected, showing stack name, repository URL, branch, compose path, and a Deploy after create checkbox" />
</Frame>

Sencho fetches the compose file, validates it with `docker compose config`, writes it to a fresh stack directory, and links the git source in one step. The last-applied commit sha is seeded from the fetch so the first pull produces a clean diff rather than a "local edits detected" warning.

Tick **Deploy after create** to run `docker compose up -d` immediately after the files land. If the deploy fails, the stack and git source are kept on disk so you can fix the underlying issue (missing image, port conflict, host resources) and retry the deploy from the editor.

### Failure modes on create

| Situation | What happens |
|-----------|--------------|
| Stack name already exists | Sencho returns **409** with "Stack already exists" and makes no changes on disk or in the database. Pick a different name or remove the existing stack. |
| Repository unreachable or auth failed | Fetch fails before anything is created. The form stays open with an error toast describing the cause. |
| Fetched compose fails validation | The stack directory is not created and no git source row is inserted. The error toast shows the `docker compose config` message. |
| Fetch + validate succeed but optional deploy fails | The stack and git source are kept. The toast reads "Stack created, but deploy failed: ..." and you can retry the deploy from the editor. |

## Configure a source

<Frame>
  <img src="/images/git-sources/panel.png" alt="Git Source panel with repository URL, branch, compose path, and apply mode" />
</Frame>

| Field | Description |
|-------|-------------|
| **Repository URL** | `https://github.com/your-org/your-repo.git` (HTTPS only) |
| **Branch** | Branch to track (e.g. `main`) |
| **Compose file path** | Path within the repo (e.g. `deploy/compose.yaml`) |
| **Also sync sibling `.env`** | When enabled, also pulls `.env` from the same directory as the compose file |
| **Auth** | `None` for public repos, `Personal Access Token` for private repos |
| **Apply behavior** | See the three modes below |

Saving runs a reachability check against the repository. If the URL is wrong, the token is invalid, the branch does not exist, or the file is missing, Sencho surfaces the error inline and nothing is persisted.

### Apply behavior modes

| Mode | What happens when a webhook fires |
|------|-----------------------------------|
| **Review only** | Sencho fetches + validates the incoming commit and marks the stack as having a pending update. You review the diff and apply manually. |
| **Auto-write** | Sencho writes the new compose + env to disk automatically but does not redeploy. Use this when another process handles rollout. |
| **Auto-deploy** | Sencho writes the files and immediately runs `docker compose up -d` so the stack picks up the new configuration. |

You can always override on the spot: when you click **Apply** in the diff dialog, a **Deploy after apply** checkbox lets you deploy regardless of the configured mode.

## Pulling and reviewing changes

Click **Pull now** on the Git Source panel to fetch the latest commit on the configured branch.

<Frame>
  <img src="/images/git-sources/diff-dialog.png" alt="Diff dialog showing a side-by-side comparison between the on-disk compose.yaml and the incoming commit" />
</Frame>

The diff dialog shows:

- The commit sha being compared (short form, next to the stack name)
- A side-by-side compare of the on-disk compose file and the incoming version
- A `.env` tab when the source is configured to sync `.env`
- A **validation** banner when the incoming compose fails `docker compose config` (you cannot apply an invalid file)
- A **local edits detected** banner when the on-disk content differs from the last applied commit. Applying in this state overwrites those edits. The Apply button becomes a confirmation prompt.

### Pending updates

When a webhook fires in **Review only** mode, the stack gets a pending update badge in the sidebar and a dot on the **Git Source** button in the editor. Clicking either opens the diff dialog with the incoming content already loaded; there's no second network round-trip to apply.

<Frame>
  <img src="/images/git-sources/sidebar-badge.png" alt="Sidebar stack entry with a small branded dot indicating a pending Git source update" />
</Frame>

Click **Dismiss** on the Git Source panel to discard a pending update without applying.

## Trigger from CI with a webhook

Git sources integrate with Sencho's existing webhook system. Create a webhook targeting the stack with the **Git source sync** action.

<Frame>
  <img src="/images/git-sources/webhook-action.png" alt="Webhook creation form with Git source sync selected in the action dropdown" />
</Frame>

The webhook's behavior on trigger depends on the source's apply mode:

- **Review only**: fetch + validate + diff, mark pending.
- **Auto-write**: fetch, validate, write to disk.
- **Auto-deploy**: fetch, validate, write, deploy.

The Git source sync action is only selectable on webhooks whose target stack already has a Git source configured. Webhook triggers for a single source are debounced so a runaway pipeline cannot overwhelm Sencho (or your repository host's rate limits); the dashboard records the skipped trigger in the webhook's execution history.

### GitHub Actions example

```yaml
- name: Sync compose via Sencho
  run: |
    BODY='{}'
    SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "${{ secrets.SENCHO_WEBHOOK_SECRET }}" | cut -d' ' -f2)
    curl -X POST "${{ secrets.SENCHO_URL }}/api/webhooks/${{ secrets.SENCHO_WEBHOOK_ID }}/trigger" \
      -H "Content-Type: application/json" \
      -H "X-Webhook-Signature: sha256=$SIGNATURE" \
      -d "$BODY"
```

See the [Webhooks](/features/webhooks) page for the full signing protocol.

## Private repositories

For private repositories, use a Personal Access Token scoped to read access on the target repo:

- **GitHub**: a fine-grained PAT with **Contents: Read** permission on the repo, or a classic PAT with the `repo` scope.
- **GitLab**: a project or group access token with the `read_repository` scope.
- **Bitbucket**: an app password with **Repositories: Read**.

Paste the token into the **Token** field and save. Sencho stores it encrypted at rest and never returns it in API responses or UI. When editing the source later, the token field shows a masked placeholder; leave it blank to keep the stored value, or type a new token to replace it. Switching the auth type to **None** clears the stored token.

## Local edits vs Git

Sencho tracks a hash of the compose + env contents at the moment of the last apply. When you pull, it compares that hash against the current on-disk content.

- Matching hash: applying overwrites content that Sencho itself last wrote.
- Differing hash: someone edited the files outside Git. The diff dialog shows a warning, and Apply requires confirmation.

The in-browser editor and the Git Source panel both write to the same files, so you can always fall back to editing locally. The next pull will just flag the divergence rather than silently clobbering your edits.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Repository not found or not accessible">
    Verify the URL is reachable from the Sencho host and ends with `.git`. GitHub returns a "not found" response for both genuinely missing repos and private repos you cannot read, so Sencho tailors the hint based on what you provided:

    - **No token configured**: the repo might be private. Switch **Authentication** to **Personal Access Token** and paste a token with read access.
    - **Token configured**: double-check the URL is correct and the token has read access to this specific repo. GitHub fine-grained PATs need **Contents: Read** on the target repo; classic PATs need the `repo` scope.
  </Accordion>

  <Accordion title="Authentication failed">
    You supplied a token and the git host rejected it outright. The token is missing, expired, or lacks read access. Generate a new token and replace the value in the **Token** field. Sencho reports this as a form error, not a Sencho login problem, so you stay signed in.
  </Accordion>

  <Accordion title="Branch not found">
    The branch name is case-sensitive and must exist on the remote. Confirm the branch with `git ls-remote <url>` from a shell that has access.
  </Accordion>

  <Accordion title="File not found">
    The compose path is relative to the repository root and must point at the file, not its parent directory. If the file was moved, update the path on the panel and save.
  </Accordion>

  <Accordion title="Compose validation failed">
    Sencho runs `docker compose config` against the incoming content before letting you apply. The error banner shows the exact message. Common causes: unresolved `${VAR}` interpolation (fix by enabling sibling `.env` sync and committing the file), invalid `include:` paths, or schema issues introduced by a recent compose change.
  </Accordion>

  <Accordion title="Local edits detected">
    The on-disk files diverge from the last applied Git commit. Either apply anyway to overwrite the local edits (the confirmation prompt makes this explicit), or discard local work with a redeploy from the stack editor, or commit your local changes back to the repo so the diff becomes clean.
  </Accordion>

  <Accordion title="Webhook skipped (rate limited)">
    Sencho debounces rapid-fire triggers per source. Wait a few seconds and retry, or consolidate multiple CI triggers into a single call at the end of your pipeline.
  </Accordion>

  <Accordion title="Network timeout">
    The clone did not finish in time. Fetches run with a bounded timeout to keep a slow or unreachable host from hanging the stack panel. Check that the Sencho host can reach the repository host (proxies, firewalls, DNS) and try again. If the repository is genuinely large, pin a smaller compose subpath or mirror it somewhere closer to the Sencho host.
  </Accordion>

  <Accordion title="Applied but deploy failed">
    The incoming compose file was written to disk successfully, but the subsequent `docker compose up -d` did not complete. The toast message includes the underlying reason (for example, an image pull failure or a port conflict). The stack is already on the new content, so you can retry the deploy directly from the editor's **Deploy** button without re-pulling. Fix the root cause first (image availability, host resources, network config) and redeploy.
  </Accordion>

  <Accordion title="Stack contents look wrong or appear empty">
    If the compose file in your repository is tracked via Git LFS, Sencho will refuse the link and surface an LFS error rather than write a pointer stub as real content. Commit the plain compose file (and any synced `.env`) without LFS, or replace the LFS pointer in-place, then retry.
  </Accordion>

  <Accordion title="A build context or volume points at an empty folder">
    Repositories that use Git submodules do not have their submodule contents cloned during a Git Source fetch. Sencho surfaces a warning on create when `.gitmodules` is present. If the compose file references paths inside a submodule (build contexts, volume mounts, include directives), inline the referenced files into the main repository or flatten the submodule so the paths resolve at deploy time.
  </Accordion>

  <Accordion title="Only HTTPS is supported">
    Git Sources fetch over HTTPS only. SSH clone URLs (`git@host:org/repo.git`) and custom protocols are rejected with a client-side validation error. Paste the `https://...` URL and use a Personal Access Token for authentication on private repositories.
  </Accordion>
</AccordionGroup>

## Known limitations

- **HTTPS only.** SSH URLs and SSH keys are not supported. Use a Personal Access Token for private repos.
- **No Git LFS.** Compose and env files stored via LFS are rejected. Commit plain files instead.
- **No submodules.** Submodule contents are not fetched; paths inside a submodule directory will be missing at deploy time. A warning is shown on create when `.gitmodules` is present.
- **Branch-tracking only.** Sources follow the head of a branch. Specific commit SHAs and tags are not pinnable.
