
<div class="callout callout-attn"><p><strong>Alpha software.</strong> Via is under active development. Host requirements, flags, manifest keys, and deploy behavior may change between releases. Use this guide for test deployments, not production workloads.</p></div>

This is the shortest manual path to install Via and deploy the `examples/echo` server. For the full operator flow, see [Via install](/docs/via/install/) and [Deploy an app](/docs/via/deploy/). This command reference assumes you have been given `echo.zip` and are testing the echo app from that unpacked source snapshot.

## What this does

This guide installs Via on a host, initializes it for one domain, creates an `echo` app, allows the app hostname, deploys the committed echo source, and runs CLI and HTTP smoke checks.

The goal is to prove the smallest useful Via deployment path end to end: host setup, TLS trust, app routing, manifest application, deploy, and request handling.

Set the hostnames and root CA filename before running the commands. The examples use `example.com`; change these values if you are using another domain.

```bash
TARGET_NAME=via-example
HOSTNAME=example.com
APP_HOSTNAME=echo.example.com
ROOT_CA_FILE=via-root-ca.crt
```

To check the download:

```bash
printf '%s  %s\n' \
  '6af0c6c1d675f36a29324ea6b31c251d6ab5ec45ade6d20cc9be177704e8d93c' \
  'echo.zip' | sha256sum -c -
```

| Role | Command | Where it runs |
|---|---|---|
| Operator | `via` | Via host |
| Developer | `vary` | Workstation |

For this example, `via` runs on the Via host and `vary` runs from the developer workstation. One person can play both roles, but keep the commands separate.

| Item | Value |
|---|---|
| Target name | `via-example` |
| Via host | `https://example.com` |
| App | `echo` |
| Public host | `echo.example.com` |
| API base | `https://echo.example.com/api/v1` |
| Route | `/api/v1` to service port `8080` |
| Smoke path | `/api/v1/echo` |
| Config/secrets | none |
| SQLite bindings | none |

## Start from a source zip

Unpack `echo.zip` in a clean working directory so the app deploy comes from the provided source snapshot.

```bash
unzip -q echo.zip
cd examples/echo/
```

The `echo.zip` archive contains `examples/echo/` with the app source, tests, project metadata, and Via app manifest you are testing.

## Operator: bootstrap Via

Install host prerequisites, install Via, initialize the host, create the admin user, create the echo app, and allow the public hostname as an app-route domain. The install/init path should leave the host deploy-ready; do not use upgrade, skipped checks, doctor repair, app-domain verification, route-ready repair, or a deploy retry as part of the normal setup path.

```bash
via install --print-prereq-script | sudo bash
via install
via init --domain "$HOSTNAME" --external-url "https://$HOSTNAME" --acme internal
# With --acme internal, Caddy mints its internal root CA lazily, on the first
# TLS handshake. Poll via-caddy until it answers (which creates the root CA)
# before exporting it; otherwise the export fails and writes an empty file.
for i in $(seq 1 30); do curl -ksS -o /dev/null "https://$HOSTNAME/" && break; sleep 1; done
via doctor
via certificate root-ca > "$ROOT_CA_FILE"
via admin create alice                    # prints a one-time adm_... token
via app ensure echo --owner alice
via domain add "$APP_HOSTNAME" --scope app --allowed-app-route-policy allow
via domain validate "$APP_HOSTNAME"
```

The `curl` loop matters: with `--acme internal`, Caddy does not create its root CA until the first HTTPS request reaches it, so `via certificate root-ca` has nothing to export until then. Skipping it leaves an empty root CA file, and the developer's `vary trust import` and every later HTTPS call fail.

Hand `$ROOT_CA_FILE` and the single-use `adm_...` token to the developer. The developer stores the token as `ADM_TOKEN`.

If `vary login` fails with a TLS alert and DNS works, check the host with:

```bash
sudo journalctl -u via-caddy.service -n 100 --no-pager
```

`NXDOMAIN` from ACME usually means the host was initialized without `--acme internal`. Recover with:

```bash
via init --domain "$HOSTNAME" --external-url "https://$HOSTNAME" --acme internal --recover
for i in $(seq 1 30); do curl -ksS -o /dev/null "https://$HOSTNAME/" && break; sleep 1; done
via certificate root-ca > "$ROOT_CA_FILE"
```

## Developer: connect

```bash
vary target add "$TARGET_NAME" "https://$HOSTNAME"
vary trust import "$HOSTNAME" --cert "$ROOT_CA_FILE"
printf '%s\n' "$ADM_TOKEN" \
  | vary login --target "$TARGET_NAME" --name alice --token-stdin
```

## Developer: stage echo in a clean repo

Via deploys from committed source. Copy only the echo app into a throwaway Git repo and commit that snapshot.

```bash
ECHO_SRC=$(mktemp -d)
cp -a . "$ECHO_SRC"
cd "$ECHO_SRC"
# The route hostname lives in via.app.toml. Repoint it before committing so it
# matches the hostname from via domain add:
sed -i "s/echo\.vary\.prod/$APP_HOSTNAME/" via.app.toml
DEPLOY_BRANCH="echo-deploy-$(date -u +%Y%m%dT%H%M%SZ)"
git init -b "$DEPLOY_BRANCH"
git config user.name "via-deploy"
git config user.email "via-deploy.example.invalid"
git add -A
git commit -m "deploy echo snapshot" >/dev/null
```

The echo app has no config, secrets, or add-ons. Its `via.app.toml` declares the HTTP listener, public route, smoke path, and rollback metadata:

```toml
[[route]]
hostname = "echo.example.com"
path_prefix = "/api/v1"
```

The `hostname` is where the deployed route comes from, not anything you pass on the command line. It must match the host's `via init --domain` value and the `via domain add` hostname, or preflight blocks the route with `domain_not_allowed`. Whether the app must also prove domain ownership before the route activates is a host setting, not a manifest field (see the next section).

## Developer: preflight, deploy, smoke

`preflight --apply-manifest --fix --approve` reads the route from `via.app.toml` and applies it to Via.

By default, this guide uses host-level app domain proof `disabled`. In that mode, the earlier `via domain add` and `via domain validate` commands are enough. Preflight will not ask for a DNS TXT token.

If the operator initialized the host with `via init --app-domain-proof required`, the operator or app owner must verify the app hostname before preflight:

```bash
via app domain verify --app echo "$APP_HOSTNAME"
```

Without that verification, preflight blocks the route with `app_domain_not_ready`.

```bash
vary target doctor --target "$TARGET_NAME"
vary app preflight echo --target "$TARGET_NAME" --workdir "$ECHO_SRC" --apply-manifest --fix --approve
```

For the full DNS TXT ownership flow when `proof_required` is on, see the [domain-proof workflow](/docs/via/domain-proof-token/).

Then deploy and smoke.

```bash
vary app deploy echo --target "$TARGET_NAME" --workdir "$ECHO_SRC" --apply-manifest --branch "$DEPLOY_BRANCH"
vary app smoke  echo --target "$TARGET_NAME"
```

A passing CLI smoke is the completion signal. Also run direct HTTP checks:

```bash
curl --cacert "$ROOT_CA_FILE" -fsS "https://$APP_HOSTNAME/api/v1/echo"
curl --cacert "$ROOT_CA_FILE" -fsS -X POST "https://$APP_HOSTNAME/api/v1/echo" \
  -H 'content-type: application/json' \
  --data '{"text":"Hello Via"}'
```

If deploy or smoke fails:

```bash
vary app status echo --target "$TARGET_NAME" --json
vary app logs   echo --target "$TARGET_NAME" --source runtime --tail 40
```

## Redeploy

For later echo changes, update the clean app repo, commit, deploy, and smoke:

```bash
cd "$ECHO_SRC"
git add -A
git commit -m "update echo" >/dev/null
DEPLOY_BRANCH="echo-deploy-$(date -u +%Y%m%dT%H%M%SZ)"
vary app deploy echo --target "$TARGET_NAME" --workdir "$ECHO_SRC" --apply-manifest --branch "$DEPLOY_BRANCH"
vary app smoke  echo --target "$TARGET_NAME"
```
