> Where a service's image comes from, how a git-based service is built, and which machine builds it.
> Source: https://zedensix.com/homerun/docs/deploy-source-and-builds · Site index: https://zedensix.com/llms.txt

# Deploy source and build methods

Where a service's image comes from, how a git-based service is built, and which
machine builds it.

## Deploy source: image or git repo

![Environments & Revisions → Source for a service built from a git repository](https://zedensix.com/images/d8c2e7ce6aa452bb9b29021089b11d928bd92ccf.webp)

Every service is either:

- **Bring-your-own-image** (the default), an image + tag, optionally with
  private registry credentials. Once the image is filled in, the **Tag** field's
  picker lists the tags its registry publishes (floating tags first, then
  versions newest first), with a filter chip per family of tags above them
  (**Releases**, **Prereleases**, variants like `*-alpine*`, numbered ones like
  `pr-*` or `sha-*`, and **Named** floating tags) so a registry full of pull
  request builds stays searchable: pick one or more chips to show only those.
  Typing any tag by hand still works.
- **Build from a git repo**, Homerun clones the repo (shallow, by branch, tag or
  full 40-character commit SHA, which pins the service to that commit) and
  builds it locally with the service's **build method**. No registry involved:
  the build produces a fresh local image tag
  (`homerun-build-<slug>:<timestamp>`) every deploy.

A repository can also carry its service's settings as a
[Homerun Manifest](https://zedensix.com/homerun/docs/manifest) (`.homerun.json`, `.homerun.yaml` or
`.homerun.yml`), applied on every deploy before the build: build method, port,
environment, resources, health check, domains, login wall and previews.

## Version pinning and update checks

An image-built service's **Environments & Revisions → Source** tab has a
**Version** panel below the source form.

**Pin version** finds the tags the running image is published under in its
registry (the same digest as the revision that's live) and swaps the floating
tag (`:latest`, `:nightly`) for the one you pick, most specific release first.
The image doesn't change, so nothing redeploys. When no published tag matches,
which is what a nightly build usually looks like, it offers to tag the running
image on this host instead, suggesting the image's
`org.opencontainers.image.version` label when it has one (the default pick is a
stable version tag, never another floating one like `:nightly`); the service's
pull policy then moves to **If missing**, since no registry has that tag. Start
a local tag with the version the build is ahead of (`1.25.0-dev`) and update
checks will spot that release when it ships. **Unpin** puts the service back on
the tag it was pinned from, picked up on the next deploy. A local tag only
exists on the manager node in [swarm mode](https://zedensix.com/homerun/docs/swarm-mode).

**Watch for updates** checks the registry every 6 hours: a version tag looks for
a newer stable release on the same track (`1.24.3` → `1.24.4`, `-rootless` stays
`-rootless`, a prerelease or `-dev` pin waits for its release, release
candidates are ignored), a floating tag for a new image behind it. A new update
sends an **Update available** [notification](https://zedensix.com/homerun/docs/notifications), once per update,
and the panel shows it with **Update now**, which moves the service to the new
tag and deploys it. **Offer** limits how far a version tag's update may go:
**Any newer release** (the default), **Minor and patch releases only** (a
service on `1.4.2` is offered `1.9.0` but never `2.0.0`) or **Patch releases
only** (`1.4.x`). Changing it forgets the last check and checks again. A
floating tag ignores it. **Check for updates**, in the panel's header, asks the
registry right away, whether or not the watch is on.

With **Try new versions before notifying** on (the default), the new version is
started first in a throwaway container with the service's env vars, command,
entrypoint, user, capabilities, devices, resource limits and networks (Homerun's
shared network and its stack's, or the swarm network for a service running in
[swarm mode](https://zedensix.com/homerun/docs/swarm-mode), so it resolves its database and the other services
it talks to). Each writable volume is copied into a fresh volume mounted at the
same path, so the new version finds its config and data without writing to the
live files; a read-only mount is shared as is. No published ports, no Traefik
route and not under the service's own name, so it never gets the live service's
traffic. Copying takes as long as the volumes are big, and the copies are
removed when the trial ends. The panel follows it with the same stepper as a
deploy (pull, start, health check, passed) and its output underneath. It passes
once it's ready by the same check a deploy gates traffic on (the service's
healthcheck command, else its port listening), or is still running after 20
seconds when healthchecks are off; the outcome, with the last log lines of a
failure, rides along in the notification. The container is removed either way;
the pulled image stays for the real deploy. Since it reaches the real database,
an app that migrates its schema on startup migrates the live one during the
trial: turn trials off for those. An app that can't start without its own data
still fails it.

A failed trial is retried automatically, up to 3 more times 30 seconds apart. If
every attempt fails the update isn't offered: there's no **Update now** button,
and the notification says the update exists but its health check fails. **Retry
trial**, on the Version panel or the dashboard, runs it again (retries included)
and notifies the new outcome; a passed trial can be rerun the same way with
**Run the trial again**.

Every service with an update waiting is listed under **Available updates** on
the dashboard, with its trial's outcome and an **Update now** button, or **Retry
trial** when its trial failed.

The same pin, watch and update are on the [REST API and the CLI](https://zedensix.com/homerun/docs/api-and-cli)
(`/api/v1/services/:id/version`, `homerun services version`), the
[MCP server](https://zedensix.com/homerun/docs/mcp) and [Terraform](https://zedensix.com/homerun/docs/infrastructure-as-code) (the watch
switches on `homerun_service`, what a check found in the
`homerun_service_version` data source).

## Build methods

![The Source section's build method picker, open on the six methods](https://zedensix.com/images/5b502367da8760c7dd47a8d762724074a97c9837.webp)

A git-based service picks how it's built in its **Environments & Revisions →
Source** section (and in the wizard):

- **Dockerfile** (the default): the repo's `Dockerfile`, or the path you set,
  relative to the build context, built with BuildKit
  (`docker buildx build --load`). Everything BuildKit supports works:
  `# syntax=` directives, `COPY --chmod`, `RUN --mount=type=cache`, multi-stage
  builds. **Target stage** picks which stage of a multi-stage Dockerfile to
  build (`--target`, the `FROM … AS <name>` name); empty builds the last one.
- **Docker Bake**: one target of a bake file, `docker buildx bake`. Set the
  **bake file** (relative to the build context, default `docker-bake.hcl`; a
  `docker-bake.json` or a compose file works too) and the **bake target**
  (default `default`). The target can be a group, as long as it resolves to a
  single image, otherwise the build fails and names the targets it found.
  Targets another target only uses as a `target:` context (their output is
  `cacheonly`) don't count. Whatever tags and outputs the target sets, Homerun
  overrides them so the image is loaded into the daemon under its own
  `homerun-build-<slug>` tag. Contexts in the file resolve from the build
  context directory.
- **Nixpacks**: detects the language and builds without a Dockerfile.
- **Railpack**: Railway's successor to Nixpacks, same idea.
- **Heroku buildpacks**: Cloud Native Buildpacks on `heroku/builder:24`.
- **Paketo buildpacks**: Cloud Native Buildpacks on
  `paketobuildpacks/builder-jammy-base`.

Every method runs as a throwaway `docker:cli` container (pinned to
`docker:29.8.1-cli`, which ships the buildx plugin) on the daemon doing the
build, with that daemon's socket mounted: `/var/run/docker.sock` on a
[build server](https://zedensix.com/homerun/docs/remote-hosts-and-agent), and Homerun's own configured socket
path for a local build, so rootless Docker works locally. A failed build's error
names its most telling line, usually BuildKit's final `ERROR:` line, and the
full output is in the deploy log. For Nixpacks, Railpack and buildpacks, the
first build on a host downloads the pinned builder binary (Nixpacks 1.41.0,
Railpack 0.39.0, pack 0.40.9) from GitHub into a `homerun-builder-tools` volume,
later builds reuse it. Both the download and the installed binary are checked
against pinned sha256 checksums, before first use and on every build, and a
mismatch fails the build rather than running an unverified binary. The build
context field still applies: point it at a subdirectory for a monorepo.
Configure the builder the way its own docs say, from the repository
(`nixpacks.toml`, `railpack.json`, `project.toml`, `Procfile`), there are no
per-builder fields here. Variables a build needs (`NEXT_PUBLIC_*` in a web
bundle, a Dockerfile `ARG`) are env vars marked
[build-time](https://zedensix.com/homerun/docs/env-vars#build-time-variables). The same methods work on
[build servers](https://zedensix.com/homerun/docs/remote-hosts-and-agent), both Docker connections and the
Homerun Agent, as long as a Docker connection's socket lives at
`/var/run/docker.sock` (rootless Docker doesn't). Nixpacks and buildpacks don't
read a build cache registry's layer cache; buildpacks keep theirs in
`pack-cache-*` volumes on the build host.

Toggle between the two in the **Source** section (also available on the New
Service wizard). Any git-clone-able HTTPS URL works, GitHub, GitLab, a
self-hosted Gitea, anywhere, since cloning doesn't need a provider-specific API.
If you've connected a git provider account (`/git-providers`, OAuth), the Source
section gets a repo-browsing picker instead of pasting a raw URL; a private repo
can also fall back to a token embedded directly in the URL
(`https://TOKEN@host/...`) without connecting a provider at all.

## Build servers and build cache

![The Source section's build context, build cache registry and build server fields](https://zedensix.com/images/2a4d7ca6dde24625676c079058e47f6ae7daad15.webp)

By default a git build runs on this host. Two optional pickers in the Source
section change that:

- **Build cache registry**: **Built-in registry** uses Homerun's own
  [registry](https://zedensix.com/homerun/docs/registry), started on first use, for builds on this host (a
  build server builds without a cache with it). Its caches survive the
  registry's daily cleanup. Or pick a registry credential registered under
  `/build-cache-registries`. Dockerfile, Docker Bake and Railpack builds use it
  as a BuildKit registry cache: `--cache-from`/`--cache-to type=registry` at
  `<registry>/homerun-build-<slug>:buildcache`, `mode=max` so intermediate
  stages are cached too. A repeat build reuses what the last one produced, even
  on a fresh builder. Both directions are best-effort: a missing cache (the
  first build) or a failed cache export logs and carries on. The registry cache
  needs BuildKit's `docker-container` driver, so these builds run on a
  persistent `homerun-cache` builder (a `buildx_buildkit_homerun-cache0`
  container on the building daemon, host networking, created on first use), and
  the image is loaded into the daemon afterwards. Builds without a cache
  registry use the daemon's own builder and its local cache.

  `<registry>` is the credential's Registry URL as entered: a host, plus the
  namespace a registry needs in front of every repository. Gitea, GitHub
  (`ghcr.io`), GitLab and Docker Hub all refuse a bare `homerun-build-<slug>` (a
  push fails with `404` on `/v2/.../blobs/uploads/`), so enter
  `git.example.com/<owner>` or `ghcr.io/<user>` there; a plain `registry:2`
  takes the host alone.

- **Build server**, a second machine that compiles the image instead of this
  one, or **Any free build server** to take the least busy one each deploy, see
  [Build servers](https://zedensix.com/homerun/docs/remote-hosts-and-agent#picking-a-build-server-and-limiting-builds).
  With a cache registry the built image is published there and pulled back;
  without one it's streamed straight back from the build server (`docker save`
  into `docker load`).

  A build server doesn't need the same CPU architecture as this host. The image
  is always built for this host's platform: an `arm64` build server building for
  an `amd64` host installs QEMU on itself on first use (one privileged
  `tonistiigi/binfmt` container, so its Docker daemon has to allow those, which
  a rootless one doesn't) and builds emulated. That works with every build
  method but is several times slower than a native build, so prefer a build
  server with this host's architecture when there's a choice. An agent older
  than this feature still builds for its own CPU, and the deploy then fails once
  the image arrives, naming both architectures: update the agent.
