---
title: Builders
description: Choose how Dekka builds a service from GitHub with Railpack, a Dockerfile, a static site build or Cloud Native Buildpacks.
section: Services
order: 4.5
updated: 2026-11-04
---

A service deployed from GitHub is built into an image before it runs, and its **Builder** decides how. You choose one when you create the service, and you can change it later under **Settings**, then **Build**. Services that run a [Docker image](/docs/docker-images) aren't built, so they have no builder.

> [!NOTE]
> During the pilot, builds run on a simulated engine. The build log shows the steps each builder would run, but no image is built and nothing is served from it.

## Which builder should I use?

| Builder | Choose it when |
| --- | --- |
| **Railpack** | Your repository is an app in a common language and has no Dockerfile. It's the default. |
| **Dockerfile** | Your repository has a Dockerfile you want Dekka to build as it is. |
| **Static** | Your build writes a folder of HTML, CSS and JavaScript, such as a Vite, Astro or Docusaurus site. |
| **Buildpacks** | Your app already builds on Heroku or with Paketo buildpacks. |

Each builder keeps its own settings, so switching to another and back leaves what you set for the first one in place.

## Railpack

Railpack looks at the repository, works out the language and framework, and builds an image without a Dockerfile. It recognises Node.js, Bun, Deno, Python, Go, Ruby, PHP, Rust and Elixir projects, including frameworks such as Next.js, Django, Rails and Phoenix.

When the detected commands aren't right, set **Build command**, such as `npm run build`, and **Start command**, such as `npm start` or `uvicorn main:app --host 0.0.0.0 --port $PORT`. Empty commands use what Railpack detects. Services created before Railpack had its name used **Automatic**, which is the same builder, and the API still accepts `auto` and stores it as `railpack`.

## Dockerfile

Dekka builds the Dockerfile in your repository with BuildKit.

| Setting | What it does | Default |
| --- | --- | --- |
| **Dockerfile** | The Dockerfile's path, relative to the root directory | `Dockerfile` |
| **Build context** | The directory sent to the build, relative to the root directory | `.` |
| **Target stage** | The stage to stop at in a multi-stage Dockerfile | the last stage |
| **Build arguments** | `KEY=value` pairs passed as `--build-arg`, one per line | none |

Paths must stay inside the repository, so they can't start with `/` or contain `..`. A service can have up to 50 build arguments. Their values are never printed in the build log, and the service's [variables](/docs/environment-variables) are available as build arguments too, so a secret your build needs can live there instead.

The image's own `CMD` starts the service unless you set a **Start command**.

## Static

The static builder runs your **Build command**, if you set one, and then serves the **Publish directory** the build writes, which is `dist` by default. Leave the build command empty to serve the repository's files as they are.

Turn on **Single-page app** for a site with client-side routing, so a path with no file of its own answers with `index.html` and the route loads in the browser. A static site is always a web service, it's served on port 80, and it runs no process of its own, so it has no start command.

## Buildpacks

Cloud Native Buildpacks detect your language and build the image the way Heroku and Paketo do, so an app that already builds there builds the same way on Dekka. **Builder image** chooses which builder picks the buildpacks.

| Builder image | Value |
| --- | --- |
| Heroku | `heroku/builder:24`, the default |
| Paketo | `paketobuildpacks/ubuntu-noble-builder` |

A `Procfile` or the buildpack's detection decides the start command unless you set one.

## Set the builder in a config file

A [`dekka.toml` or `dekka.json`](/docs/config-as-code) in the service's root directory can set the builder and its settings in the `build` section, using these names.

| Setting | Name |
| --- | --- |
| Builder | `strategy`, one of `railpack`, `dockerfile`, `static` or `buildpacks` |
| Dockerfile | `dockerfile_path` |
| Build context | `dockerfile_context` |
| Target stage | `dockerfile_target` |
| Build arguments | `build_args`, a table of strings |
| Publish directory | `static_publish_dir` |
| Single-page app | `spa_fallback` |
| Builder image | `buildpacks_builder` |

```toml title="dekka.toml" tab="Dockerfile"
[build]
strategy = "dockerfile"
dockerfile_path = "docker/api.Dockerfile"
dockerfile_target = "runner"

[build.build_args]
NODE_ENV = "production"
```

```toml title="dekka.toml" tab="Static"
[build]
strategy = "static"
build_command = "npm run build"
static_publish_dir = "build"
spa_fallback = true
```

```json title="dekka.json" tab="Buildpacks"
{
  "build": { "strategy": "buildpacks", "buildpacks_builder": "paketobuildpacks/ubuntu-noble-builder" }
}
```

In the API, the same names are fields of a service's `spec.build`.

## Workers and scheduled jobs

A [worker or scheduled job](/docs/service-types) has no port, so it can't use the static builder. A worker built from a Dockerfile, or one that runs a Docker image, starts with the image's own command, so its **Start command** is optional. Railpack and Buildpacks workers, and every scheduled job, need a start command, because there's nothing else to tell Dekka what to run.
