Skip to content
Coritan Docs

How Apps works

How an app turns a commit or an image into healthy replicas in your regions, switches visitors to them and keeps them running.

View as Markdown

An app is a web service we run from its source. Each version you run is a deployment: one image, started as replicas in the app's regions. A new deployment goes live only once its replicas pass their health check, and the one before it keeps serving until then.

An app has one of two sources, chosen when you create it:

Git
A repository on a public host, reached over HTTPS, and a branch (main unless you choose another). Each deployment builds one commit into an image. For a private repository, save an access token with the app. An app in a monorepo can name its directory inside the repository.
Image
A container image reference, such as ghcr.io/example/web:1.4.0. Each deployment runs that image as it is, with no build. The image must be one that anyone can pull, because an app holds no registry credentials.

The source type cannot change after you create the app. To move an app from an image to git, or back, create a new app.

A git deployment starts with a build. The build fetches the one commit, without its history, from the app's directory. It does not fetch submodules, and files kept in Git LFS arrive as pointer files.

The build then uses one of two methods:

Dockerfile
Used when you set a Dockerfile path, or when the app's directory holds a file named Dockerfile. A path is looked for in the app's directory first, then at the root of the repository. The Dockerfile defines the whole build, so the install, build and start commands are not used.
Railpack
Used when there is no Dockerfile. Railpack reads the files in the app's directory and works out how to install, build and start it. The install, build and start commands you set replace the ones it would choose.

Environment variables marked for the build reach it as follows. With Railpack, the build sees each one as an environment variable. With a Dockerfile, a variable that is not secret is a build argument, which the Dockerfile reads after declaring it with ARG NAME, and a secret one is a build secret with the variable's name as its id, as in RUN --mount=type=secret,id=NAME. A plain variable whose name starts with BUILDKIT_ is left out of a Dockerfile build. Set environment variables covers the rest.

The build log hides the repository's access token and every secret build value of six characters or more, which appear as [redacted]. A build that runs for more than an hour is stopped and fails.

A deployment starts when you create an app (unless you ask it not to), when you deploy, redeploy or roll back, and when the push webhook receives a push to the app's branch. It then moves through these statuses, which the dashboard shows with a capital, as in Ready, and spells canceled as Cancelled:

Status Meaning
queued Waiting for a build to start. Only a git deployment is queued.
building The image is being built.
deploying Replicas are starting in each region. An image deployment, a redeploy and a rollback start here.
ready Every region has its healthy replicas, and the deployment serves the app.
failed The build failed, or a region could not bring up its replicas. The deployment's page, and its error field, say why.
canceled You cancelled it, or it stopped because a newer deployment went live or the app was suspended or deleted.
superseded It was ready and served the app until a newer deployment went live.

A deployment that fails or is cancelled leaves the current one serving. When a deployment goes live, any older deployment that is still on its way is cancelled. Deploy an app shows each way to start one.

Each replica runs with the memory, CPU and disk of the app's size:

Size Memory CPU Disk
small 512 MB 1 vCPU 2 GB
medium 1 GB 2 vCPU 4 GB
large 2 GB 4 vCPU 8 GB

An app runs in every region that can run apps, written ["*"], or in up to 20 regions you name by their codes, such as fra. With ["*"], the regions are settled each time a deployment starts, so a new region joins at the next deployment. min_replicas sets how many replicas run in each region, from 1 to 5. max_replicas is saved with the app, but the number of replicas does not grow above min_replicas.

A replica starts once its image is in place. We then send it GET requests for the app's health check path (/ unless you set another) on the port in its PORT variable. An answer counts when it arrives within 5 seconds with a status from 200 to 399. We do not follow redirects, so a 301 or 302 counts as a pass. Two passes in a row make the replica healthy.

A replica fails when its image is not in place within 20 minutes of being planned, or when it is not healthy within 10 minutes of starting. We replace a failed replica. A region fails when it has had two more failed replicas than min_replicas (three for one replica), or when it has no room for a replica for a minute.

A deployment goes live once every region has min_replicas healthy replicas. Every domain of the app then switches to the new replicas at once. The previous deployment's replicas stop taking requests, stop about a minute later, and are removed 30 minutes after they stopped taking requests. A rollback within that time starts them again instead of placing new ones.

An app on ["*"] goes live without a region that failed, as long as another region is ready. The deployment's error then names the region left out, as in Left out sin: No app node in sin has room for another replica of this deployment. An app with named regions goes live only when every one of them comes up, and otherwise fails with Replicas could not start in … and the reason for each region.

About once a minute, we check every serving replica's health check path. A replica that fails a check is marked unhealthy and keeps serving. After three failed checks in a row, it stops taking requests and we start a replacement in its region. When every replica of the app is failing, they all keep serving instead of leaving the app with none. We stop replacing replicas in a region once min_replicas plus two of them have failed within an hour, and start again when fewer have.

Routes to an app carry WebSocket connections as well as ordinary requests.

A redeploy runs the current deployment's image again, with the app's settings and environment variables as they are now. A redeploy with a rebuild builds the app's branch again, or for an image app deploys its saved image reference again.

A rollback is a new deployment of an earlier deployment's image. Only a deployment that went live can be rolled back to. It runs with the environment variables as they are now, in the app's regions as they are now, and build-time values stay as they were when that image was built. Roll back to an earlier deployment has the details.

  • Listen on the port in the PORT environment variable, on every interface (0.0.0.0). An app that listens only on 127.0.0.1 fails its health checks. We choose the port for each replica, and the app's port setting does not change it.
  • Answer GET on the health check path with a status from 200 to 399 within 5 seconds, once it is ready to take requests.
  • Keep nothing on its disk that must outlive a deployment. Use a database or Object Storage.
  • Exit when it receives SIGINT, which is how we stop a replica. A replica that does not exit is stopped by force.

Every replica also gets these variables, which you cannot set yourself:

PORT
The port to listen on.
CORITAN_APP
The app's name on the platform, such as web-shop.
CORITAN_REGION
The code of the replica's region, such as fra.
CORITAN_DEPLOYMENT
The ID of the deployment the replica runs.