Golden Path: Install Via and the Echo Server

This is the shortest manual path to install Via and deploy the examples/echo server. For the full operator flow, see Via install and Deploy an app. 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.

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

To check the download:

printf '%s  %s\n' \
  '6af0c6c1d675f36a29324ea6b31c251d6ab5ec45ade6d20cc9be177704e8d93c' \
  'echo.zip' | sha256sum -c -
RoleCommandWhere it runs
OperatorviaVia host
DevelopervaryWorkstation

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.

ItemValue
Target namevia-example
Via hosthttps://example.com
Appecho
Public hostecho.example.com
API basehttps://echo.example.com/api/v1
Route/api/v1 to service port 8080
Smoke path/api/v1/echo
Config/secretsnone
SQLite bindingsnone

Start from a source zip

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

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.

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:

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

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

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

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.

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:

[[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:

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

Without that verification, preflight blocks the route with app_domain_not_ready.

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.

Then deploy and smoke.

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:

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:

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:

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"