Local development stack
The repository ships a complete local stack for working on Infrahub Sync: a disposable Infrahub instance, PostgreSQL and MinIO for the service's own records and artifacts, a dedicated Prefect server, and the Sync HTTP API and its Prefect worker running from your checkout.
Everything the stack creates is disposable, and every credential it uses is a published local default. Never point any value on this page at a shared or internet-facing instance.
Verified 2026-09-16 against source revision
61b6a1b9dccae637b522084f563858dfcd5e31a9. The commands, ports,
paths and restart behavior below were derived by reading, at that exact revision,
tasks/preview.py and
development/preview.env. They were not established by a live
replay of the stack.
Install
The stack needs Docker, uv, and Python 3.11 to 3.13.
uv sync --extra dev --extra prefect --extra service
All three extras are required. Without prefect and service the Sync API and its
worker cannot start, and the type checker cannot resolve their imports.
Commands
| Command | What it does |
|---|---|
uv run invoke preview.up | Start the containers and the host processes. Writes nothing to Infrahub. |
uv run invoke preview.seed | Write the smoke dataset into Infrahub. |
uv run invoke preview.smoke | Seed, then run the smoke suite, which creates and applies real runs. |
uv run invoke preview.status | Show container, host process, and endpoint state. |
uv run invoke preview.logs | Print the tail of a host process log. |
uv run invoke preview.down | Stop the host processes and the containers, keeping the data. |
uv run invoke preview.down --volumes | Stop everything and delete every data volume. |
preview.logs prints the Sync API log by default; pass -n prefect-worker for the
worker's, and --lines for how much of it to print.
Starting the stack writes nothing
preview.up brings the stack up and stops there. It loads no schema, creates no branch
or node, and admits no run, so starting it again against an environment you are already
using changes none of your data.
Two commands write, and each says what it will write before writing:
preview.seedloads the example schema fromexamples/prefect_remote_run/schemas/infra_device.yml, creates theInfraDevicenamedcore01onmain, and then forks thepreview-smokebranch from it. It changes nothing on an environment that already holds that branch.preview.smokeseeds, then runs the smoke suite. That suite mutatescore01onmainand drives real plan and apply runs againstpreview-smoke.
A clean preview.up followed by preview.smoke therefore works without a separate seed.
The service development loop
This is the loop for working on infrahub_sync/service/ itself: start the stack, drive it,
change code, get the change running, and tear it down.
Start
uv sync --extra dev --extra prefect --extra service
uv run invoke preview.up
preview.up starts the containers and waits for Infrahub and Prefect to answer, refuses if it
finds retired state, creates the Prefect work pool, starts the Prefect worker, applies the
service deployment, starts the Sync API, and waits for it to serve its OpenAPI document. It
then prints the addresses, the bearer principals, the configuration directory and the runtime
state directory.
Two details of that startup shape the rest of the loop:
- The worker runs from its own empty directory,
.preview/worker-cwd. Prefect puts the working directory at the front ofsys.pathwhen it resolves a deployment's module entrypoint, so a worker started at the repository root would import your checkout instead of the installed distribution. Starting it from an empty directory is what proves the service resolves its flow as an installed module. - The configuration directory the Sync API reads is the repository's
examples/directory. That is where it looks for configurations, so the example packages are available without copying anything.
Inspect
uv run invoke preview.status # containers, host processes, endpoints
uv run invoke preview.logs # the Sync API log, last 50 lines
uv run invoke preview.logs -n prefect-worker # the worker's log
uv run invoke preview.logs -n prefect-worker --lines 500
preview.status prints each container, then each host process as running (pid N) or
stopped, then the three addresses. The two host processes are sync-api and
prefect-worker; their logs and process identifiers live under .preview/.
Edit: there is no auto-reload
A code change does not reach the running stack by itself. Two things combine to make that true, and neither is a defect:
- The Sync API is started under
uvicornwithout--reload, so it does not watch your files. - The task that starts a process checks first and returns early when that process is already
running, printing
already running. So re-runningpreview.upafter an edit leaves the old process in place.
To run changed service code, stop and start:
uv run invoke preview.down
uv run invoke preview.up
preview.down without --volumes stops the two host processes and the containers and
keeps every data volume. Your Infrahub instance, the service records and the artifacts all
survive, so the cycle above costs a restart and not a re-seed.
Stop and restart, and the one destructive reset
| Command | Host processes | Containers | Data volumes |
|---|---|---|---|
uv run invoke preview.down | Stopped | Stopped | Kept |
uv run invoke preview.down --volumes | Stopped | Stopped | Deleted |
--volumes is the destructive form. It deletes every data volume in the stack, so the
Infrahub instance, the service records and the artifacts are all gone and the next preview.up
starts empty. Use it when you want a clean slate or when the startup refusal on retired state
tells you to — not as part of the ordinary edit cycle.
Smoke
uv run invoke preview.smoke
Seeds, then runs the smoke suite against the running stack. It writes: see Starting the stack writes nothing above for exactly what. Every test in the suite skips when the stack is unreachable, so read the collection summary before treating a green run as evidence. Testing tiers covers what each suite proves.
Change a port or a token
Put the override in development/preview.local.env, which the tasks read after the shipped
development/preview.env and which Git ignores. Never edit preview.env for a personal
change. Overrides take effect at the next preview.up, so stop the stack first if it is
running.
Tear the stack down
uv run invoke preview.down --volumes
Removes everything the stack created. The .preview/ directory keeps its logs.
Addresses
| Service | Address | Runs as |
|---|---|---|
| Infrahub | http://localhost:8080 | Container |
| Prefect | http://localhost:4210 | Container |
| Sync HTTP API | http://127.0.0.1:8010 | Host process |
| PostgreSQL | 127.0.0.1:5439 | Container |
| MinIO | http://127.0.0.1:9010 | Container |
The defaults avoid ports 8000 and 4200, so an Infrahub development stack or a Prefect
server you already run keeps working alongside. To change any of them, put the override
in development/preview.local.env, which the tasks read after the shipped
development/preview.env. Git ignores that local file.
Runtime state — process identifiers, logs, and caches — lives under .preview/ at the
repository root, which Git also ignores.
Every credential is a development default
Nothing the stack uses is a secret, and none of it works anywhere else:
development/preview.envships the MinIO access key and secret key, the Infrahub admin token, and the Sync API bearer principal.development/docker-compose.infrahub.ymlis based on the official Infrahub Compose file athttps://infrahub.opsmill.io/<VERSION>for theVERSIONindevelopment/preview.env. This repository adds image digest pins to it; when refreshing from upstream, restore and verify those pins. Its Infrahub admin token, agent token, and security key defaults are the upstream published values.
They reach only the disposable local containers this stack creates. Do not reuse them in
a deployment, and mint your own tokens in development/preview.local.env if you need
different ones.
When updating an Infrahub or Prefect image in the preview, change its tag and
matching @sha256: digest together in development/preview.env. Inspect the
tag with docker buildx imagetools inspect name:tag, copy the top-level index
digest, and check that it includes both linux/amd64 and linux/arm64. Compose
would still pull the old image after a tag-only change, so the preview tasks
refuse one, including a Prefect tag exported in the shell. The shipped
preview.env value takes precedence over an unrelated shell VERSION. An
Infrahub image-name-only override in development/preview.local.env or the
shell drops the shipped digest so a local build can run. To use a registry mirror, set its digest
explicitly; the mirror may use the same digest as the shipped image. When using
Docker Compose directly, an image-name override drops the shipped digest even
without VERSION or an env file. With that override, an empty
INFRAHUB_DOCKER_IMAGE_DIGEST leaves the image unpinned. A digest-only override
keeps the shipped Infrahub tag and replaces its digest.
The stack refuses to start on retired state
preview.up starts the containers and waits for Infrahub and Prefect to answer, then
reads Prefect and the host process list before it starts the worker, the deployment, and
the Sync API. It refuses at that point when it finds state left under the names this
service used before its rename: the infrahub-sync-managed deployment, a work pool or
worker whose name starts with it, or a host process running infrahub_sync.managed.serve
or infrahub_sync.managed.worker.
The refusal names its own fix:
uv run invoke preview.down --volumes
That reset is destructive. It deletes every data volume in the stack, so the Infrahub instance, the service records, and the artifacts all go. It also stops a retired host process it can identify without ambiguity; where more than one running process matches a retired name, it stops none of them and asks you to stop them by hand.
preview.down without --volumes does neither. It leaves the data volumes and any
retired process in place, so it does not clear the refusal.
Running the tests
The smoke suite lives in tests/preview/ and is opt-in:
uv run pytest -m preview tests/preview -q
Run it in a single process. Its modules share one Infrahub branch and one Prefect
deployment, and a collection hook orders the run-creating modules ahead of the module
that observes their Prefect flow runs. Under pytest-xdist that ordering would apply
only within one worker, and the shared branch would take concurrent writes.
Every test in the suite skips, rather than fails, when the stack is not reachable.
The rest of the test suite needs no stack. Run the offline default, which deselects every marker that writes to one:
uv run invoke tests.tests-unit
Use that form while the stack is up. A plain pytest -q would collect the smoke suite against
your running environment, and the integration tests write to whatever Infrahub instance the
ambient settings name. Testing tiers is the single
description of each suite, its prerequisites and what a skip means.