Skip to content
Coritan Docs

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.

View as Markdown

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.

  • 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). Production releases from git work on every plan.
  • Keep the repository on GitHub, or see 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.
  1. In the dashboard, 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). 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).

Order your code describes each section. To order with the API instead, have an access token ready (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 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 once the deployment exists instead.
  4. When the answer says "requires_payment": true, pay its invoice (Pay an invoice).

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

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.

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.

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.

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.
Shell
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.

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 uploaded, runs the ignore command, then the install and build commands with the variables the build gets (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).

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

Section titled Dockerfile? Deploy a container image

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). 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).

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

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). 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.

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.

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.

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:

Shell
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 operations on this page

MethodPathWhat it does
GET/api/v1/git/github/installStart a GitHub App install
GET/api/v1/git/github/installationsList installations
POST/api/v1/git/github/installationsRecord a GitHub installation
DELETE/api/v1/git/github/installations/{installation_ref}Forget a GitHub installation
GET/api/v1/git/github/installations/{installation_ref}/repositoriesList an installation's repositories
GET/api/v1/client/deployments/{deployment_uuid}/gitGet the repository connection
PUT/api/v1/client/deployments/{deployment_uuid}/gitBuild the deployment from a repository one of the owner's GitHub App installations reaches
PATCH/api/v1/client/deployments/{deployment_uuid}/gitChange the repository settings
DELETE/api/v1/client/deployments/{deployment_uuid}/gitStop cloning through the GitHub App
GET/api/v1/client/deployments/{deployment_uuid}/previewsList previews
DELETE/api/v1/client/deployments/{deployment_uuid}/previewsClose previews
POST/api/v1/client/deployments/{deployment_uuid}/previews/{release_uuid}/promotePromote preview
GET/api/v1/client/deployments/{deployment_uuid}/hooksList deploy hooks
POST/api/v1/client/deployments/{deployment_uuid}/hooksCreate deploy hook
DELETE/api/v1/client/deployments/{deployment_uuid}/hooks/{hook_id}Delete a deploy hook
GET/api/v1/deploy-templatesList deploy templates
GET/api/v1/deploy-templates/{slug}Get deploy template
GET/api/v1/deploy-buttonDeploy button
GET/api/v1/deploy-presetsDeploy presets
GET/api/v1/orgs/{org_slug}/git/github/installStart a GitHub App install
GET/api/v1/orgs/{org_slug}/git/github/installationsList installations
POST/api/v1/orgs/{org_slug}/git/github/installationsRecord a GitHub installation
DELETE/api/v1/orgs/{org_slug}/git/github/installations/{installation_ref}Forget a GitHub installation
GET/api/v1/orgs/{org_slug}/git/github/installations/{installation_ref}/repositoriesList an installation's repositories
GET/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/gitGet the repository connection
PUT/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/gitBuild the deployment from a repository one of the owner's GitHub App installations reaches
PATCH/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/gitChange the repository settings
DELETE/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/gitStop cloning through the GitHub App
GET/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/previewsList previews
DELETE/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/previewsClose previews
POST/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/previews/{release_uuid}/promotePromote preview
GET/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/hooksList deploy hooks
POST/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/hooksCreate deploy hook
DELETE/api/v1/orgs/{org_slug}/deployments/{deployment_uuid}/hooks/{hook_id}Delete a deploy hook