# Deploy from a git repository

> Order a deployment that builds from git, connect GitHub, choose the production branch, and get a preview of every branch and pull request.

Source: https://www.coritan.com/docs/managed-containers/deploy-from-git/

In the dashboard:

- /dashboard/deployments/…/git: https://www.coritan.com/dashboard/deployments

A web service, a static site or a hybrid site can build from a git repository. Each push to the repository's production branch builds a new production release. Connect GitHub, and a push to any other branch, or a pull request, can build a *preview*: a release of its own at an address of its own, which never replaces production. A new release goes live once it passes its health check, and the release serving now keeps serving until then.

## Before you begin

- Order it from the dashboard's **New deployment**, when its order page offers **What to deploy**. Without it, deploying from git is not open yet, and the API answers `404` for it too.
- Previews need a plan that includes them: Pro, Business or Enterprise ([Pricing](https://www.coritan.com/pricing)). Production releases from git work on every plan.
- Keep the repository on GitHub, or see [Other git hosts](#other-git-hosts) for GitLab, Gitea, Forgejo and Gogs.
- To connect a GitHub organization's repositories, you need to be one of its owners on GitHub. In a Coritan organization, owners and admins connect GitHub; members can see the connection.

## Order the deployment {#order}

1. In the [dashboard](https://www.coritan.com/dashboard/deployments), go to **App Deployment** and select **New deployment**.
2. Under **What to deploy**, select **Git repository**. Under **Repository**, choose **From GitHub** and select the repository, connecting GitHub first when the page asks, or choose **Paste an address** and paste its `https` address.
3. Under **Build and run**, leave a field blank and the build works it out, or set the **Framework**, the **Root directory** of a monorepo, the commands and the **Port** your code listens on. Choosing a framework fills in its commands, output directory and port, which you can change ([Frameworks we build](#frameworks)). **How it is served** chooses **A server**, or **Hybrid** when the build also writes files for the edge to serve.
4. Choose the **Size** of each instance, where it runs under **Locations**, the **Deployment name** under **Networking**, the **Production branch** and **Previews** under **Git**, and how it is billed under **Billing**.
5. Select the button under the summary, and pay the invoice when the order asks you to ([Place the order](/docs/managed-containers/order-a-server/#place-the-order)).

[Order your code](/docs/managed-containers/order-a-server/#order-your-code) describes each section. To order with the API instead, have an access token ready ([Authentication](/docs/api/authentication/)):

1. Write the order's body: `kind` `git`, the repository's `https` address in `source.git.repo_url` and its production branch in `source.git.branch` (`main` when left out). Add `source.git.root_dir` when the app is in a directory of the repository, as in a monorepo, and `runtime.port` for the port it listens on. [Order any kind](/docs/managed-containers/order-a-server/#order-any-kind) shows a whole body.
2. Send it to `POST /checkout/quote` to see what it costs. The quote writes nothing.
3. Send the same body to `POST /services/order`. For a private repository on another git host, add an access token that can read it in `source.git.token`. On GitHub, [connect GitHub](#connect-github) once the deployment exists instead.
4. When the answer says `"requires_payment": true`, pay its invoice ([Pay an invoice](/docs/billing/invoices/)).

The deployment appears under **App Deployment**, and its first release builds the newest commit on the production branch.

## Connect GitHub {#connect-github}

Connect the GitHub App so that each push builds on its own, a private repository needs no token, and each pull request gets its preview addresses.

1. Open the deployment and select the **Git** tab.
2. On the **Repository** card, select **Connect GitHub…**.
3. If the dialog says **Install the GitHub App first**, select **Install on GitHub**. GitHub asks where to install the Coritan app: on your own GitHub account or one of your GitHub organizations, for every repository or only the ones you select. Approve the install, and GitHub sends you back to the tab.
4. Choose the **GitHub account**, then the repository. **Search repositories** finds it by name.
5. Leave **Production branch** empty for the repository's default branch, or type another. Fill in **Directory** when the app is in a directory of the repository.
6. Select **Connect**.

The **Repository** card then says the repository is reached **Through the GitHub App**. The installation belongs to your Coritan account, or to the organization you were working in, and every deployment it owns can use it. It asks GitHub to read the contents of the repositories you chose (to clone them), to read and write commit statuses (to show each release on its commit) and pull requests (to comment a preview's addresses), and to read their metadata. To reach another repository later, change the installation's repositories on GitHub, or select **Install the App on another account** in the dialog.

**Change repository…** connects another repository, and **Disconnect…**, in the card's menu, goes back to cloning the repository by its address.

## Turn on previews {#turn-on-previews}

On the deployment's **Git** tab, turn on **Make previews** on the **Previews** card. From then on:

- A push to a branch other than the production branch builds a preview of that branch.
- A pull request builds a preview of its newest commit when it is opened, reopened or pushed to. A pull request from a fork is never built.
- Each preview answers at two addresses under the platform's domain for deployments (these pages use `apps.example.net`): the branch's newest ready preview at `<branch>--<deployment>.apps.example.net`, such as `feature-login--web-shop.apps.example.net`, and that commit's own release at `<first 7 characters of the commit>--<deployment>.apps.example.net`. A branch name becomes lowercase letters, digits and single hyphens, so `feature/Login` is `feature-login`, and a name longer than 63 characters is shortened and keeps a short hash.
- A preview runs one instance in the deployment's first location, and sleeps when idle.
- With **Comment on pull requests** on, the commit gets a status that links the preview, and the pull request gets one comment with both addresses, edited in place as the preview changes.

The card lists each branch with a preview, its latest release and its address. Deleting the branch, or closing or merging the pull request, closes its previews: their instances stop and their addresses stop answering. **Close previews…** in a branch's menu does the same at once, and the next push to the branch builds a new one.

To send a preview to production without building it again, select **Promote to production…** in its branch's menu. The preview keeps answering at its own addresses.

## Choose which pushes build {#choose-which-pushes-build}

A monorepo does not need a release for every push. The **What a push builds** card on the **Git** tab holds the production branch, the directory and two settings that decide. Select **Save changes** after you change them.

**Watch paths**
: Up to 50 patterns, one a line, from the repository's root. A push builds when at least one file it changed matches. `*` matches within one directory, `**` across any number of them, a trailing `/` takes everything under a directory, and `!` leaves files out, such as `!apps/web/docs/**`. A later pattern wins over an earlier one. A push that matches nothing builds nothing, and its commit gets the status `Skipped: no changed file matches the watch paths`. A forced push always builds, because it does not list its files.

**Ignore command**
: One line of up to 500 characters that runs in the repository after checkout, such as `git diff --quiet HEAD^ HEAD -- apps/web`. When it exits `0`, the release is not built and says `Skipped: the ignore command exited 0, so this release was not built.` It runs only for a release a push made: a release you deploy yourself, a deploy hook or a rollback always builds.

## Deploy with a hook {#deploy-hooks}

A deploy hook is an address that builds one branch each time something sends it a `POST`, such as a CMS that publishes or a scheduled job. A deployment has up to 20.

1. On the **Deploy hooks** card of the **Git** tab, select **Add a deploy hook…**.
2. Type a **Name**, and a **Branch** unless the hook builds the production branch.
3. Select **Add hook**.
4. Copy the address the dialog shows. It is shown only this once, so keep it secret: anyone who has it can start a build. To replace a lost address, delete the hook and add another.

```bash
curl -X POST "$DEPLOY_HOOK_URL"
```

Each call builds the branch's newest commit, as a production release for the production branch and as a preview for any other, which needs previews on. A call that arrives while a release of the same branch is still waiting to build joins it instead of building the same commit twice. A `GET` does nothing, so a link unfurled in a chat builds nothing. Each hook takes 30 calls in 5 minutes.

## How a release is built {#how-it-builds}

Each build runs in a build container of its own, made for that build and removed when it ends. The container checks out the commit, or unpacks what [the CLI](/docs/managed-containers/cli/) uploaded, runs the ignore command, then the install and build commands with the variables the build gets ([Environments and variables](/docs/managed-containers/environments-and-variables/)). The build log follows it as it runs, with secret values hidden.

What the build leaves depends on how the deployment is served:

- A static site's output directory goes to every edge location.
- A web service's directory, with what the install and build commands wrote into it, is packed once and becomes the release's build output. Each instance starts from it, on the runtime the build used (such as Node 22), and runs the start command. A rollback, a redeploy and a promoted preview start from the same build output without building again.
- A hybrid site does both.

The deployment's dependency caches, such as `node_modules/.cache`, `.next/cache` and pip's cache, are kept between builds, up to 1 GB, so the next build installs faster. Build minutes count while the build runs ([How App Deployment is billed](/docs/managed-containers/how-billing-works/)).

### Frameworks we build {#frameworks}

The build detects the framework from the repository: `package.json` and its lockfile, `requirements.txt` or `pyproject.toml`, and `go.mod`. A **Framework** you choose, and every command you set, win over what it detects. The commands below are the defaults; Node commands run with the package manager the lockfile names (npm, pnpm, Yarn or Bun), and a start command can use `$PORT`.

| Framework | Preset | Builds as | Build command | Start command | Output | Port |
| --- | --- | --- | --- | --- | --- | --- |
| Vite | `vite` | Static site | `npm run build` | none | `dist` | none |
| Astro | `astro` | Static site | `npm run build` | none | `dist` | none |
| Astro (server) | `astro-ssr` | Web service, hybrid site | `npm run build` | `node ./dist/server/entry.mjs` | `dist/client` | 4321 |
| Next.js | `next` | Web service, static site, hybrid site | `npm run build` | `npm run start` | `out` | 3000 |
| Nuxt | `nuxt` | Web service, static site, hybrid site | `npm run build` | `node .output/server/index.mjs` | `.output/public` | 3000 |
| Remix | `remix` | Web service, static site, hybrid site | `npm run build` | `npm run start` | `build/client` | 3000 |
| SvelteKit | `sveltekit` | Web service, static site, hybrid site | `npm run build` | `node build` | `build` | 3000 |
| Hono | `hono` | Web service | `npm run build` | `npm run start` | none | 3000 |
| Express | `express` | Web service | none | `npm start` | none | 3000 |
| FastAPI | `fastapi` | Web service | none | `uvicorn main:app` on `$PORT` | none | 8000 |
| Flask | `flask` | Web service | none | `gunicorn app:app` on `$PORT` | none | 8000 |
| Django | `django` | Web service | `manage.py collectstatic` | `gunicorn` with the project's `wsgi.py` | none | 8000 |
| Go | `go` | Web service | `go build -o ./app .` | `./app` | none | 8080 |
| Static files | `static` | Static site | none | none | the directory itself | none |

The install command is `npm install` for Node, a virtual environment and `pip install -r requirements.txt` for Python, and `go mod download` for Go. Node builds on version 22 unless `engines.node` in `package.json` or an `.nvmrc` asks for 20 or 24. Python builds on 3.12, with 3.11 and 3.13 also available, and Go on 1.23. A Node framework not in the table, such as Angular or Docusaurus, builds as a static site when you set its build command and output directory.

`GET /api/v1/deploy-presets` answers the same table, with each preset's versions.

## Dockerfile? Deploy a container image {#dockerfile}

Dockerfiles are not built. A repository whose build names a Dockerfile, or a deployment with a Dockerfile path saved from before, is refused with `422` and `dockerfile_unsupported`, and its release fails with `Dockerfiles are not built on the platform. Build the image in your CI and deploy it as a container image.`

To keep your Dockerfile:

1. Build the image in your CI, such as a GitHub Actions workflow that runs `docker build` and pushes the image to GitHub Packages or Docker Hub. The image must be public.
2. Order a deployment with **Container image** under **What to deploy**, and type the image, such as `ghcr.io/you/web:1.4.0` ([Order your code](/docs/managed-containers/order-a-server/#order-your-code)). When an order is refused for its Dockerfile, **Deploy a container image instead** on the order page switches it to a container image.
3. For each new version, push a new tag and start a release with it ([Start a release](/docs/managed-containers/releases/#start-a-release)).

Or remove the Dockerfile and let the build detect the framework, from the table above.

## Other git hosts {#other-git-hosts}

A repository on GitLab, Gitea, Forgejo or Gogs builds the same way without the GitHub App. Order it with its `https` address under **Paste an address**, and an **Access token** when it is private (`source.git.token` with the API), then add the deployment's push webhook to the repository so that each push builds a release ([Deploy on every push](/docs/apps/push-webhook/)). Branch previews, watch paths, the ignore command and deploy hooks work for these repositories too. Previews of pull requests, commit statuses and pull request comments need the GitHub App.

## Result

Each push to the production branch shows on the **Releases** tab as a new release, with its commit, its build log and its state, and production moves to it once it is ready. Previews show on the same tab and on the **Previews** card. If a release fails, production stays on the release before it.

## Troubleshooting

A push built nothing
: Check the watch paths and the ignore command. A push to a branch other than the production branch builds only while previews are on, and a pull request from a fork is never built.

GitHub says an organization owner must approve the install
: Only an owner of the GitHub organization can install the app there. Ask one to connect it from the Coritan account, or the Coritan organization, that the deployments belong to.

The repository is not in the list
: The installation reaches only the repositories you chose on GitHub. Add the repository to the installation there.

**Previews come with a paid plan**
: Previews are part of the Pro, Business and Enterprise plans. Change your plan, or release to production.

**New previews are held**
: Previews are on, but the plan of the account the deployment bills to no longer includes them, so a push to another branch builds nothing. The previews already live keep serving. Change to a plan that includes previews.

## Related

- [Environments and variables](/docs/managed-containers/environments-and-variables/) gives production and previews different values.
- [Host a static site](/docs/managed-containers/static-sites/) covers the output directory, redirects and headers.
- [Use the Coritan CLI](/docs/managed-containers/cli/) deploys a directory from your computer or from CI.
- [Deploy on every push](/docs/apps/push-webhook/) sets up the push webhook for other git hosts.

## With the API

The routes on this page are under `/api/v1/client/deployments/{uuid}` for your own deployment and `/api/v1/orgs/{org_slug}/deployments/{uuid}` for an organization's. Owners and admins of an organization change them; any member reads them.

`GET /api/v1/git/github/install` answers the GitHub address to send the browser to, in `url`. GitHub sends the browser back to the dashboard with a ticket, and `POST /api/v1/git/github/installations` with `{"ticket": "..."}` records the installation. `GET /api/v1/git/github/installations` lists your installations, each with how many deployments clone through it, and `GET /api/v1/git/github/installations/{id}/repositories` lists the repositories one reaches, 30 to a page. `DELETE /api/v1/git/github/installations/{id}` forgets an installation: the deployments that cloned through it keep their repository's address and clone it as a pasted repository, and the GitHub App stays installed on GitHub until you uninstall it there. For an organization, the same routes are under `/api/v1/orgs/{org_slug}/git/github`.

`PUT /git` connects a deployment to a repository, and `PATCH /git` changes only the fields it names:

```bash
curl -X PATCH https://api.coritan.com/api/v1/client/deployments/$DEPLOYMENT/git \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"watch_paths": ["apps/web/**", "!apps/web/docs/**"]}'
```

`GET /previews` lists each branch with a preview, its pull request, the release it serves and its addresses. `POST /previews/{release}/promote` makes a production release of a ready preview, and `DELETE /previews?branch=feature-login` closes a branch's previews. `POST /hooks` with `{"name": "CMS", "branch": "main"}` creates a deploy hook and answers its `url` this once.

`GET /api/v1/deploy-templates` lists the starters, such as Astro, Nuxt, Vite with React, Vue or Svelte, and Hono, each with its repository and the part of an order's `config` it decides, and `GET /api/v1/deploy-templates/{slug}` answers one. Put a starter's `slug` in `source.starter` to order a copy of it. Neither needs a token.

`GET /api/v1/deploy-button?repo=<https address>` answers the order address for a public repository, in `path` on the dashboard and in `url` on coritan.com, for a Deploy button in a README. `branch` (`main` when left out) and `root`, the app's directory, narrow it. An address a build could not clone answers `422` with `invalid_repository`, and the refused word in `field`.

## API

- `GET /api/v1/git/github/install`: Start a GitHub App install (https://www.coritan.com/docs/api/reference/client/github/#op-get-api-v1-git-github-install)
- `GET /api/v1/git/github/installations`: List installations (https://www.coritan.com/docs/api/reference/client/github/#op-get-api-v1-git-github-installations)
- `POST /api/v1/git/github/installations`: Record a GitHub installation (https://www.coritan.com/docs/api/reference/client/github/#op-post-api-v1-git-github-installations)
- `DELETE /api/v1/git/github/installations/{installation_ref}`: Forget a GitHub installation (https://www.coritan.com/docs/api/reference/client/github/#op-delete-api-v1-git-github-installations-installation-ref)
- `GET /api/v1/git/github/installations/{installation_ref}/repositories`: List an installation's repositories (https://www.coritan.com/docs/api/reference/client/github/#op-get-api-v1-git-github-installations-installation-ref-repositories)
- `GET /api/v1/client/deployments/{deployment_uuid}/git`: Get the repository connection (https://www.coritan.com/docs/api/reference/client/deployments/deployments-git/#op-get-api-v1-client-deployments-deployment-uuid-git)
- `PUT /api/v1/client/deployments/{deployment_uuid}/git`: Build the deployment from a repository one of the owner's GitHub App installations reaches (https://www.coritan.com/docs/api/reference/client/deployments/deployments-git/#op-put-api-v1-client-deployments-deployment-uuid-git)
- `PATCH /api/v1/client/deployments/{deployment_uuid}/git`: Change the repository settings (https://www.coritan.com/docs/api/reference/client/deployments/deployments-git/#op-patch-api-v1-client-deployments-deployment-uuid-git)
- `DELETE /api/v1/client/deployments/{deployment_uuid}/git`: Stop cloning through the GitHub App (https://www.coritan.com/docs/api/reference/client/deployments/deployments-git/#op-delete-api-v1-client-deployments-deployment-uuid-git)
- `GET /api/v1/client/deployments/{deployment_uuid}/previews`: List previews (https://www.coritan.com/docs/api/reference/client/deployments/deployments-previews/#op-get-api-v1-client-deployments-deployment-uuid-previews)
- `DELETE /api/v1/client/deployments/{deployment_uuid}/previews`: Close previews (https://www.coritan.com/docs/api/reference/client/deployments/deployments-previews/#op-delete-api-v1-client-deployments-deployment-uuid-previews)
- `POST /api/v1/client/deployments/{deployment_uuid}/previews/{release_uuid}/promote`: Promote preview (https://www.coritan.com/docs/api/reference/client/deployments/deployments-previews/#op-post-api-v1-client-deployments-deployment-uuid-previews-release-uuid-promote)
- `GET /api/v1/client/deployments/{deployment_uuid}/hooks`: List deploy hooks (https://www.coritan.com/docs/api/reference/client/deployments/deployments-hooks/#op-get-api-v1-client-deployments-deployment-uuid-hooks)
- `POST /api/v1/client/deployments/{deployment_uuid}/hooks`: Create deploy hook (https://www.coritan.com/docs/api/reference/client/deployments/deployments-hooks/#op-post-api-v1-client-deployments-deployment-uuid-hooks)
- `DELETE /api/v1/client/deployments/{deployment_uuid}/hooks/{hook_id}`: Delete a deploy hook (https://www.coritan.com/docs/api/reference/client/deployments/deployments-hooks/#op-delete-api-v1-client-deployments-deployment-uuid-hooks-hook-id)
- `GET /api/v1/deploy-templates`: List deploy templates (https://www.coritan.com/docs/api/reference/client/app-deployment/#op-get-api-v1-deploy-templates)
- `GET /api/v1/deploy-templates/{slug}`: Get deploy template (https://www.coritan.com/docs/api/reference/client/app-deployment/#op-get-api-v1-deploy-templates-slug)
- `GET /api/v1/deploy-button`: Deploy button (https://www.coritan.com/docs/api/reference/client/app-deployment/#op-get-api-v1-deploy-button)
- `GET /api/v1/deploy-presets`: Deploy presets (https://www.coritan.com/docs/api/reference/client/app-deployment/#op-get-api-v1-deploy-presets)
- `GET /api/v1/orgs/{org_slug}/git/github/install`: Start a GitHub App install (https://www.coritan.com/docs/api/reference/organizations/organization-github/#op-get-api-v1-orgs-org-slug-git-github-install)
- `GET /api/v1/orgs/{org_slug}/git/github/installations`: List installations (https://www.coritan.com/docs/api/reference/organizations/organization-github/#op-get-api-v1-orgs-org-slug-git-github-installations)
- `POST /api/v1/orgs/{org_slug}/git/github/installations`: Record a GitHub installation (https://www.coritan.com/docs/api/reference/organizations/organization-github/#op-post-api-v1-orgs-org-slug-git-github-installations)
- `DELETE /api/v1/orgs/{org_slug}/git/github/installations/{installation_ref}`: Forget a GitHub installation (https://www.coritan.com/docs/api/reference/organizations/organization-github/#op-delete-api-v1-orgs-org-slug-git-github-installations-installation-ref)
- `GET /api/v1/orgs/{org_slug}/git/github/installations/{installation_ref}/repositories`: List an installation's repositories (https://www.coritan.com/docs/api/reference/organizations/organization-github/#op-get-api-v1-orgs-org-slug-git-github-installations-installation-ref-repositories)
- `GET /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/git`: Get the repository connection (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-git/#op-get-api-v1-orgs-org-slug-deployments-deployment-uuid-git)
- `PUT /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/git`: Build the deployment from a repository one of the owner's GitHub App installations reaches (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-git/#op-put-api-v1-orgs-org-slug-deployments-deployment-uuid-git)
- `PATCH /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/git`: Change the repository settings (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-git/#op-patch-api-v1-orgs-org-slug-deployments-deployment-uuid-git)
- `DELETE /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/git`: Stop cloning through the GitHub App (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-git/#op-delete-api-v1-orgs-org-slug-deployments-deployment-uuid-git)
- `GET /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/previews`: List previews (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-previews/#op-get-api-v1-orgs-org-slug-deployments-deployment-uuid-previews)
- `DELETE /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/previews`: Close previews (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-previews/#op-delete-api-v1-orgs-org-slug-deployments-deployment-uuid-previews)
- `POST /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/previews/{release_uuid}/promote`: Promote preview (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-previews/#op-post-api-v1-orgs-org-slug-deployments-deployment-uuid-previews-release-uuid-prom)
- `GET /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/hooks`: List deploy hooks (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-hooks/#op-get-api-v1-orgs-org-slug-deployments-deployment-uuid-hooks)
- `POST /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/hooks`: Create deploy hook (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-hooks/#op-post-api-v1-orgs-org-slug-deployments-deployment-uuid-hooks)
- `DELETE /api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/hooks/{hook_id}`: Delete a deploy hook (https://www.coritan.com/docs/api/reference/organizations/organization-deployments/deployments-hooks/#op-delete-api-v1-orgs-org-slug-deployments-deployment-uuid-hooks-hook-id)
