# Quickstart

URL: https://antifailure.dev/docs/getting-started/quickstart

From an empty machine to a working environment, and what each command actually did.

---

This goes from nothing to a running environment on your own machine. It needs
Docker. A Postgres connection string you are allowed to read from is optional:
with one, every environment holds a masked copy of that database, and without
one it holds the schema your migrations create. It does not need an account, a
control plane, or a cloud provider.

The whole sequence:

```bash
curl -fsSL https://antifailure.dev/install.sh | sh
af runner install  # the agent runner, which drives a real browser and needs node
af init            # reads your repo, writes antifailure.yaml
af golden refresh  # only if the manifest names a production database: set
                   # that variable first, and this makes the masked copy once
af up              # database branch from the golden, built services, sealed network
af test            # agents run your workflows and return verdicts with evidence
af down            # every resource it created, gone
```

`af start` says whether the refresh, the one conditional step, is yours.

## Install

```bash
curl -fsSL https://antifailure.dev/install.sh | sh
```

The installer downloads the release for your platform, checks it against the
published checksum, and puts `af` and its runner under `~/.antifailure`. It is
POSIX `sh` rather than bash, so it works in an Alpine container as well as on a
laptop. The file served at that URL is the [source in the repository](https://github.com/antifailure/antifailure/blob/main/install.sh).

### Installing a particular release

The installer finds out which release is the newest by following the redirect on
[github.com/antifailure/antifailure/releases/latest](https://github.com/antifailure/antifailure/releases/latest),
which points at the tag GitHub marks as the latest release. To install a
different one, name its tag:

```bash
curl -fsSL https://antifailure.dev/install.sh | AF_VERSION=v1.6.0 sh
```

`AF_VERSION` is also the way through if the installer cannot work out which
release is the newest, and it tells you which of those things happened rather
than guessing. Nothing answering at all, an address that has asked GitHub for
too much, a repository with no published release, and a release with no build
for your platform are four different sentences, because only some of them are
worth trying again.

### What it does to your PATH

`~/.antifailure/bin` is on nobody's PATH by default, so the installer puts it
there. It appends one line to the file your login shell reads at startup,
prints that line, and names the file:

```
Added this to ~/.zshrc, so every new terminal finds af:

  export PATH="$HOME/.antifailure/bin:$PATH"
```

Delete that line to undo it. zsh gets `.zshrc` under `ZDOTDIR`, bash gets
`.bash_profile` on macOS and `.bashrc` on Linux, fish gets `fish_add_path` in
`config.fish`, and a shell the installer does not recognise is told so rather
than having a file guessed for it. Running the installer again does not add the
line a second time.

The current terminal cannot see the file just written, so the installer ends
with one line to paste that fixes that shell and runs the first command:

```bash
export PATH="$HOME/.antifailure/bin:$PATH" && af start
```

To manage PATH yourself, decline in advance. Nothing is written, and the
installer prints the full path to `af`:

```bash
curl -fsSL https://antifailure.dev/install.sh | AF_NO_MODIFY_PATH=1 sh
```

In GitHub Actions no profile is touched at all: the installer writes to
`GITHUB_PATH`, so `af` resolves in every later step of the job.

### Installing somewhere else

`AF_PREFIX` moves the whole installation, both the binary and the runner the
release ships with:

```bash
curl -fsSL https://antifailure.dev/install.sh | AF_PREFIX=/opt/antifailure sh
```

`AF_BIN_DIR` moves the binary on its own, and is the one to reach for when you
want `af` in a directory that is already on your PATH:

```bash
curl -fsSL https://antifailure.dev/install.sh | AF_BIN_DIR=$HOME/.local/bin sh
```

The runner goes beside it, in `share/antifailure/runner` next to the directory
you named, so `~/.local/bin` puts it in `~/.local/share/antifailure/runner`.
That is where `af` looks for the runner it shipped with, relative to itself,
and the PATH line the installer prints names the directory you chose. Both of these want a directory you can
write to without `sudo`; if the write fails the installer says which path it
could not write and stops rather than installing half of a release.

### On Windows

In PowerShell, either the Windows PowerShell every machine has or PowerShell 7:

```powershell
irm https://antifailure.dev/install.ps1 | iex
```

It is the same installer with the same promises, written for PowerShell rather
than translated into it. It finds the newest release the same way, refuses a
download that does not match `checksums.txt` or that `checksums.txt` does not
name, and says what GitHub answered when something does not arrive. It
installs the build for your machine's architecture, `amd64` or `arm64`, and on
an Arm laptop it asks the machine rather than the PowerShell process, so an
emulated x64 shell still gets the native build.

`af.exe` goes in `%USERPROFILE%\.antifailure\bin` and the runner in
`%USERPROFILE%\.antifailure\share\antifailure\runner`. The bin directory is
added to your user PATH, as `%USERPROFILE%\.antifailure\bin` so a moved
profile does not leave a dead entry, and to the terminal you ran it in, so
`af start` works straight away. Remove the entry under Edit environment
variables for your account to undo it. The settings are the same as on the
other platforms, set as environment variables first:

```powershell
$env:AF_VERSION = '<tag>'; irm https://antifailure.dev/install.ps1 | iex
```

A release from before the Windows builds existed has no zip to install, and
the installer says that the release does not include a build for Windows rather
than installing something else.

`AF_PREFIX`, `AF_BIN_DIR` and `AF_NO_MODIFY_PATH` work as they do above, and in
GitHub Actions the bin directory goes to `GITHUB_PATH` instead. To upgrade, run
the same line again: Windows will not overwrite a running program, so an
`af.exe` that an editor holds open as its MCP server is moved aside and the
new one takes its name. The next `af` to start removes the old one once nothing
is running it, as it does after `af update`.

Environments run in Linux containers, so Docker Desktop has to be in its Linux
containers mode, which is its default. `af doctor` says so when it is not.

`af.exe` is not code signed yet. Installed this way it carries no mark of
having been downloaded, which is what SmartScreen's warning keys on. A zip saved
from the releases page in a browser does carry that mark, and the binary
extracted from it can be stopped with "Windows protected your PC"; run
`Unblock-File` on the zip before extracting it.
On Windows 11 with Smart App Control turned on, an unsigned program can be
refused outright, and the way through is to install from WSL instead.

### In WSL

WSL 2 answers as Linux, so the Linux installer is the one to use there, and it
installs the Linux build:

```bash
curl -fsSL https://antifailure.dev/install.sh | sh
```

`install.sh` run from Git Bash, MSYS2 or Cygwin is not Linux, and it points
you at `install.ps1` rather than installing anything.

## Find out where you are

```bash
af start
```

You can run this at any point. It reports every step below as observed on this
machine right now, and names the single next command.

```
Your first run
  ok    af on your PATH              ~/.antifailure/bin/af
  ok    Docker                       version 28.5.1, linux containers
  ...   the agent runner             runner: no runner at ~/.antifailure/runner
  ...   a manifest                   no antifailure.yaml here or in any parent directory
  skip  the database source          after the manifest
  skip  masking rules                after the manifest
  skip  a golden                     after the manifest
  skip  an environment               after the manifest
  skip  workflows to run             after the manifest
  ok    a model key                  none set, so agents use the deterministic planner
  skip  evidence on disk             after the manifest
  ok    nothing left behind          none are being held

Next

    af runner install
```

It runs nothing and writes nothing, and every answer comes from the machine
rather than from a record of what it last did.

Five states, and it never collapses one into another. `ok` was observed to be
finished. `...` was observed not to be, and is where you are. `warn` is
something missing that the next command does not need: the variable naming
production, when a verified golden for this project already exists.
`fail` is something broken that has to be fixed before the next command can
work. `skip` is a step it deliberately did not look at, and it says why and
what to run instead. With the Docker provider the golden step is answered from
the daemon, selected by the same rule `af up` uses, so it never names a golden
made for another project or one that was never verified; with a hosted provider
it is skipped, because that listing needs credentials and this branch's lock.

Exit 0 means every step is either done or not reached yet, which is the normal
state of a first run in progress. Exit 3 means something is broken.

## Check the machine

```bash
af doctor
```

`af doctor` is the wider check: disk, ports, DNS, outbound reachability, kernel
isolation, proxy settings, git, and the environments this machine is still
holding. Every problem it names carries what to do about it.

It also validates the manifest when one exists and compares a stable CLI version
with the latest published GitHub release, with a three second network timeout.
An outdated version or invalid manifest fails the check. No network, a development
build, or no manifest is reported explicitly rather than as a pass, and a missing
manifest does not fail the check.

```bash
af update
```

This downloads the latest stable release for this platform, verifies its published
checksum, and replaces the installed binary and its bundled runner source. The old
binary stays in place until the replacement is ready. Shell profiles and project
files are left alone. If a package manager owns the binary, upgrade through that
manager instead. Enterprise binaries use their enterprise distribution, not the
public community release. Afterwards, run `af runner install` to refresh the installed
runner and `af doctor` to check the installation. To see the latest release without
changing files:

```bash
af update --check
```

## Install the agent runner

```bash
af runner install
```

The runner drives a real browser, so it is a separate program in a separate
language and it needs node 22.6 or newer. It is copied from the source that
ships beside `af` rather than downloaded, and its dependencies come from the
lockfile that ships with it. It then downloads chromium, which is the slow part.

```bash
af runner check
```

reports each thing separately: the source, every dependency the runner declares
against what is actually under `node_modules`, whether the lockfile pinned them,
node against the range the runner requires, and the browser. It does not claim
the runner executes. Anything it cannot determine it reports as not checked
rather than as ok.

It reports on the runner `af test` would use from where you are standing, and
prints that path. A run looks for a runner in your own checkout before it looks
at `~/.antifailure/runner`, and it takes the nearest one that can actually run
rather than the nearest one that exists, so a `runner/` directory whose
dependencies were never installed is passed over. The check names the directory
it went past and says what is missing from it.

A failed browser download is not fatal. Until a browser arrives, a workflow that
needs a page read comes back `unverified`.

Everything up to `af up` works without the runner; only `af test` needs it.

## Describe the repository

```bash
af init
```

Detection reads the repository and writes `antifailure.yaml`: the services it
found, the port each listens on, the migration command, and a network policy
derived from the SDKs in your dependency list. If your `package.json` has
`stripe` in it, the Stripe hosts arrive in the manifest without being asked.

It never executes anything from the repository: detection reads files.

Anything it is unsure about becomes a question rather than a silent guess, and
everything it reports names the file it came from. You can answer the questions
without a prompt if you are scripting it:

```bash
af init --non-interactive
```

That accepts every default and prints what it assumed.

Read the manifest before going further. The
[manifest reference](/docs/reference/manifest) explains every key.

## Name the database to copy, if there is one

`af init` writes `database.source_url_env` only when the repository already
names its production variable, so read the `database` block it wrote. If it
names a variable, put production's read only connection string there, in this
shell, in `.env`, or in the encrypted store, and build the golden once:

```bash
af secret set PRODUCTION_DATABASE_URL   # reads the value without echoing it
af golden refresh                       # copies, masks, verifies, and commits it
```

The value is read on this machine for one `pg_dump` and never written anywhere
an environment can reach. The refresh runs `masking.yaml` over the copy, or the
built in rules when there is no file, and refuses to commit a golden the
verifier found sensitive data in. [Goldens](/docs/concepts/goldens) and
[masking](/docs/concepts/masking) cover both.

If the block names no variable, skip this. The first `af up` builds the golden
itself, from `database.seed` when the manifest sets one and otherwise empty, and
every branch after that is made from it. Skip it as well when
`af start` reports a golden already made for this project: `af up` branches that
one, and the variable is needed by the next refresh rather than by you now.

## Look at what would happen

```bash
af explain
```

This resolves the manifest and prints the plan: which golden a branch would come
from, what each service would build from, and the mode every host in the network
policy has been given. Nothing is created.

## Bring an environment up

```bash
af up
```

That builds the services, creates a branch of the golden, and starts everything
inside a network namespace that reaches nothing except the hosts your policy
allows. The first run is the slow one, because the images are built. Later runs
branch from what already exists.

While it runs, or afterwards:

```bash
af status
af logs
```

## Run the workflows

```bash
af test
```

Agents drive the application the way a person does, through the accessibility
tree, and return one of five verdicts for each workflow in the manifest with a
video, a trace, and steps to reproduce it.

The verdict that matters is `blocked`. A browser that crashed, a page that never
loaded, or a persona with no password is not evidence about your application. Of
the five verdicts, only a failure exits non zero. A run that never reached a
verdict exits on the configuration problem that stopped it.

```
  ok    sign in                      pass in 4.1s
  ok    place an order               pass in 11.7s

  2 passed, 0 failed, 0 flaky, 0 blocked, 0 unverified, in 16s
```

A manifest that declares no workflows is refused rather than reported as a run
that examined nothing. `af start` says so before `af up`.

### The evidence

Everything a run produced is under `.antifailure/artifacts/<environment>` in
the repository: a video and a Playwright trace per workflow, screenshots, the
console log, and the list of requests the page could not make, which is usually
the egress policy doing its job. `af start` reports whether anything is there.

### A model key is optional

```bash
af model show
```

Nothing above needs one. With no key the agents plan deterministically, the
workflows still run, and the verdicts are real. A key lets an agent read a page
it has not seen before, and where one is set it is reported by fingerprint, from
which source, and whether it has been checked.

```bash
af model set anthropic
```

reads the key without echo and puts it in your operating system's keyring. It is
never passed on a command line, never written to the manifest, and there is no
command that prints it back.

## Prove the containment

```bash
af net policy
```

prints the decision for every host the policy knows, and

```bash
af net explain GET https://api.stripe.com/v1/charges
```

answers for one specific request: which rule matched, which mode it is in, and
what would happen. If something reached the network unexpectedly,
`af net log` has the record of it, including the denials.

The modes are covered in [egress](/docs/concepts/egress). `BLOCK` refuses with a
decision you can read, `SANDBOX` swaps in test credentials and trips a wire if a
live key ever appears, `CAPTURE` records mail and messages into an inbox your
tests can read, and `MOCK` answers from an offline pack with no network at all.

## Tear it down

```bash
af down
```

Everything it created is removed, and the removal is checked rather than
assumed. If a previous run was killed halfway, the journal reconciles it: see
[the journal](/docs/concepts/journal) for why that matters and
`af env prune` for sweeping up after a machine that lost power.

## What to read next

[Goldens](/docs/concepts/goldens) and [masking](/docs/concepts/masking) are the
two ideas everything else rests on: how a masked copy of production is built
once and branched cheaply, and how identifiers are replaced deterministically so
the same customer is the same fake customer in every table and every refresh.

[Verification](/docs/concepts/verification) explains why an unverified golden
cannot be branched at all.

[Building services](/docs/guides/build) covers what happens when detection
guessed wrong about how your services are built.

[Watching a run](/docs/guides/dashboard) is the live view: `af up --hud` draws
the same run as a dashboard, and where there is no terminal it writes one line
per event instead.

## Running it somewhere other than your laptop

Everything above is the same wherever the engine runs.

[An environment per pull request](/docs/getting-started/pull-requests) is
Antifailure inside GitHub Actions: the same `af up`, in a workflow, with one
comment on the pull request that is updated in place rather than appended to.
If the checkout had a GitHub remote, `af init` already wrote that workflow
beside the manifest, and committing it is the whole setup. No server is needed.
[GitHub](/docs/guides/github) is the reference behind it: the two modes, what
the App must be granted, forks, and teardown.

[The control plane](/docs/self-hosting/control-plane) is the optional hosted
piece. Read it when you want environments that outlive a workflow run, a shared
address for them, or a record across repositories.
