# How Push-to-Deploy Works in Openship: From GitHub Webhook to Live Deployment

> Learn how push-to-deploy in Openship automates deployments from GitHub. Discover how webhooks, payload processing, and service routing make your code live instantly.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: internals
- Published: 2026-07-23

---

**Push-to-deploy in Openship automatically triggers deployments when you push code to GitHub by registering webhooks, processing push payloads at a unified inbound endpoint, and intelligently routing changes only to affected services.**

Push-to-deploy (also called auto-deploy) is the core mechanism in the [oblien/openship](https://github.com/oblien/openship) repository that transforms GitHub push events into live deployments. This feature bridges your Git repository and the Openship deployment engine, enabling continuous delivery without manual intervention. Understanding the internal flow—from webhook registration to service routing—helps you debug deployment failures and optimize your CI/CD pipeline.

## Enabling Auto-Deploy for a Project

Before Openship can react to pushes, you must toggle the **`autoDeploy`** flag and establish a webhook connection. This process validates your project configuration and implements one of four webhook strategies based on your GitHub integration method.

### The Configuration Endpoint

Enabling auto-deploy starts at the API layer in [`apps/api/src/modules/projects/project.routes.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/projects/project.routes.ts). The route `POST /api/projects/:id/auto-deploy` invokes the `setAutoDeploy` controller:

```typescript
// project.routes.ts (line 212)
r.post("/:id/auto-deploy", …, ctrl.setAutoDeploy)

```

The controller validates the project and delegates to `resolveWebhookStrategy` to determine how Openship will receive GitHub events.

### Webhook Strategy Resolution

Openship supports four distinct strategies for receiving push events, defined in [`apps/api/src/modules/projects/project.controller.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/projects/project.controller.ts):

*   **Strategy "app"** – Relies on the Openship GitHub App, which already receives all repository events. The code simply updates the database flag: `await repos.project.update(id, { autoDeploy: enabled })` (lines 82-86).
*   **Strategy "domain"** – Creates a shared webhook that posts to a verified custom domain (`domainWebhookUrl`). The `ensureSharedWebhook` function registers the webhook and persists its ID (lines 88-101).
*   **Strategy "repo"** – Creates a repository-level webhook directly on GitHub using the same `ensureSharedWebhook` helper, but without a custom domain (lines 106-112).
*   **Strategy "none"** – Disables the feature entirely.

When disabling auto-deploy, the controller calls `disableSharedWebhookIfUnused` (lines 27-44) to clean up shared webhooks if no other projects in the same repository require them. Every change is recorded via `audit.recordAsync(..., { action: "autoDeploy.set", … })` for compliance tracking.

## Receiving GitHub Push Events

Once enabled, GitHub delivers push notifications to a unified inbound endpoint that validates signatures and dispatches events to the appropriate handler.

### The Unified Inbound Endpoint

All GitHub webhooks route through `/api/webhooks/github`, implemented in the webhook service layer. After signature verification, the system constructs a **WebhookHandlerContext** and delegates push events to the `handlePush` function.

### The handlePush Handler

Located in [`apps/api/src/modules/github/webhook-push.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/github/webhook-push.ts), the `handlePush` function extracts repository metadata and delegates to the deployment engine:

```typescript
// webhook-push.ts (lines 44-75)
export async function handlePush(payload: GitHubPushPayload): Promise<WebhookHandlerResult> {
  const owner = payload.repository?.owner?.login;
  const repo  = payload.repository?.name;
  const ref   = payload.ref;
  const branch = ref.replace("refs/heads/", "");
  
  return triggerBranchDeployments({ owner, repo, branch, … });
}

```

This handler filters out non-branch refs (like tags) and immediately hands off to `triggerBranchDeployments`, the core auto-deploy orchestrator.

## Matching Projects and Smart Routing

The deployment decision logic lives in `triggerBranchDeployments` and its helper `deployProjectFromPush`, which determine exactly which services need rebuilding.

### Finding Local Projects

First, the system queries for all projects referencing the pushed repository:

```typescript
// webhook-push.ts (line 45)
const projects = await repos.project.findByGitRepo(input.owner, input.repo);

```

It then filters this list to include only projects where:
1.  **`autoDeploy`** is enabled, **and**
2.  The project's configured branch matches the pushed branch (`projectWebhookBranch` matches `input.branch`)

If no local projects match, the system falls back to `forwardPushToCloud` (lines 57-70), which forwards the event to the Openship Cloud SaaS via `cloudFetchAsOrgOwner` for processing in the managed control plane.

### Intelligent Service Routing

For matching projects, `deployProjectFromPush` executes a series of optimization steps:

1.  **Load Services**: Retrieves enabled services via `repos.service.listByProject(p.id)` (lines 11-13).
2.  **Extract Changes**: Calls `extractChangedFiles` to parse the push diff via the GitHub Compare API (lines 27-30).
3.  **Force-All Detection**: Checks if the changed-file set is truncated or if a "force-deploy-next" flag is set. If true, `forceAll` triggers a full project redeploy (lines 42-66).
4.  **Monorepo Routing**: Uses `routeServicesByChanges(routableServices, extracted.files)` (lines 67-79) to determine which specific services are affected by the commit. If no services match the changed files, the deployment is skipped to save resources.
5.  **Persist Metadata**: Stores the changed file paths in the deployment row via `repos.deployment.setChangedPaths(...)` (lines 22-29) for visibility in the dashboard UI.

If this process fails before a deployment row is created, `notifyAutoDeployFailed` emits a `deployment.failed` notification to prevent silent failures.

## Triggering the Deployment

The final step invokes the core deployment engine. The `triggerDeployment` function (residing in [`apps/api/src/modules/deployments/build.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/deployments/build.service.ts)) receives:
*   The project ID
*   Target branch
*   Optional commit SHA
*   List of specific service IDs (or "all")
*   The list of changed files

This engine handles the actual build, containerization, and rollout processes. You can monitor the result via the dashboard, which polls `/api/projects/:id` to display fields like `latestDeploymentId` and `latestDeploymentStatus`.

## End-to-End Example

Consider a typical workflow enabling auto-deploy via CLI and triggering a deployment:

### Enable via CLI

```bash
openship project git auto-deploy proj_123 --enable

```

This command POSTs to `/api/projects/proj_123/auto-deploy`, executing the `setAutoDeploy` flow described above.

### GitHub Delivers Payload

When you push to `main`, GitHub POSTs to `https://your-openship-instance.com/_openship/hooks/github`:

```json
{
  "ref": "refs/heads/main",
  "repository": {
    "owner": { "login": "myorg" },
    "name": "my-app",
    "default_branch": "main"
  },
  "head_commit": {
    "id": "a1b2c3d4",
    "message": "Add feature"
  }
}

```

The server executes `handlePush` → `triggerBranchDeployments` → `deployProjectFromPush` → `triggerDeployment`.

### Dashboard Result

The project configuration now reflects:

```json
{
  "auto_deploy": true,
  "webhook_strategy": "repo",
  "latestDeploymentId": "dep_987",
  "latestDeploymentStatus": "success"
}

```

## Summary

*   **Enablement flow**: The `setAutoDeploy` controller in [`project.controller.ts`](https://github.com/oblien/openship/blob/main/project.controller.ts) toggles the database flag and configures one of four webhook strategies (app, domain, repo, or none).
*   **Inbound processing**: GitHub push events hit `/api/webhooks/github`, where `handlePush` in [`webhook-push.ts`](https://github.com/oblien/openship/blob/main/webhook-push.ts) validates payloads and extracts branch metadata.
*   **Project matching**: `triggerBranchDeployments` queries projects by repository, checks the `autoDeploy` flag, and matches branches before proceeding.
*   **Smart routing**: `routeServicesByChanges` analyzes file diffs to deploy only affected services in monorepo setups, falling back to full redeploys when necessary.
*   **Execution**: The `triggerDeployment` function in the build service initiates the actual container build and rollout.

## Frequently Asked Questions

### What webhook strategies does Openship support for push-to-deploy?

Openship supports four strategies defined in [`project.controller.ts`](https://github.com/oblien/openship/blob/main/project.controller.ts): **"app"** (uses the existing GitHub App installation), **"domain"** (creates a shared webhook posting to a custom verified domain), **"repo"** (creates a repository-specific webhook directly on GitHub), and **"none"** (disables auto-deploy). The strategy is automatically resolved based on your GitHub integration type when you call the auto-deploy endpoint.

### How does Openship determine which services to deploy in a monorepo?

After matching the project and branch, Openship calls `extractChangedFiles` to retrieve the diff from GitHub's Compare API. It then passes these files to `routeServicesByChanges`, which maps file paths to specific services based on your configuration. Only services with matching file changes are deployed, unless the diff is truncated or a force-deploy flag is set, in which case all services are rebuilt.

### What happens if auto-deploy is enabled but no local project matches the pushed repository?

If `triggerBranchDeployments` finds no local projects matching the pushed repository and branch, it executes `forwardPushToCloud`. This helper forwards the webhook payload to Openship Cloud SaaS using `cloudFetchAsOrgOwner`, allowing cloud-linked projects to process the deployment even when the self-hosted instance doesn't have a local database record for the project.

### Where is the push-to-deploy logic implemented in the Openship codebase?

The core logic spans three main files: [`apps/api/src/modules/projects/project.controller.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/projects/project.controller.ts) (enablement and webhook registration), [`apps/api/src/modules/github/webhook-push.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/github/webhook-push.ts) (payload handling and routing decisions), and [`apps/api/src/modules/deployments/build.service.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/modules/deployments/build.service.ts) (the `triggerDeployment` function that executes the actual build and deployment).