This is the text your agent reads after apth skills install. It is the operating manual for one application: repository, image, stack, database, publish.
One AppThrust application = a GitHub repository + a container image + an
ApplicationStack (web component + PostgreSQL + connection) + an optional
public URL. Everything below goes through apth (developer CLI) and gh.
Never call the Platform API with curl when an apth command exists.
edit code → git push main → GitHub Actions builds ghcr.io/<org>/<repo>:edge-<ts>
→ AppThrust observes the newest edge- tag → releases it (~1–3 min after push)
→ https://<name>.appthrust.dev/
Measured on appthrust.dev (2026-09-16, nextjs-postgres template): apps create → stack Ready + public URL serving in 3.5 min; a later git push →
image built → released in about 1 min. The whole "template → own game →
verified on the public URL" loop fits in 5–6 minutes when the steps below are
overlapped; do not wait for one step when the next does not depend on it.
Prerequisites (check, do not assume)
apth --help >/dev/null # CLI present (build: go build -o ~/.local/bin/apth ./apth-cli/cmd/apth in appthrust/platform)
gh auth status # GitHub CLI logged in with push access to the org (appthrust)
node --version # 24.x matches the template Dockerfile
apth whoami # authenticated + project set; see Login
apth skills install # refresh this skill after upgrading apth (add --force when it reports a diff)
Default environment for AppThrust development: API https://dashboard.appthrust.dev,
project pj-af59d1dc379b (name sample), placement zone free-apne1-shared-2,
publish domain appthrust.dev. Confirm with apth projects list and
apth placement-zones list when unsure; never guess a different environment.
1. Login
apth login --api-url https://dashboard.appthrust.dev --project pj-af59d1dc379b
login without --token starts a device login: it prints a URL such as
https://dashboard.appthrust.dev/cli-login?user_code=XXXX and waits. Show that
URL and code to the human, ask them to approve it in a browser, and keep the
command running (default wait 10 minutes). The token is stored in
~/.config/appthrust/credentials.json for the profile and is never
printed. apth whoami asks the API whether the stored token is still
accepted: tokenValid: true plus login means go; authenticated: false
with a hint means run apth login again (Dashboard CLI tokens last about
12 hours). Any command failing with HTTP 401 means the same thing.
Non-interactive alternatives:
APTH_TOKEN=<token> apth ...orapth login --token <token>when a token was issued elsewhere.- Service account:
printf '%s\n' "$SECRET" | apth login --issuer https://iam.appthrust.dev/realms/appthrust --client-id <client> --client-secret-stdin --profile svc. This is the only credential the actor gateway accepts (see Actors).
Persist defaults so later commands are short: apth context set --project <pj>.
2. Create the application
Pick a name that is a DNS label ([a-z0-9-], ≤ 63 chars); it becomes the
GitHub repo, the image path, and the subdomain.
A. Template path (fastest; AppThrust creates the repo)
apth apps create <name> --template nextjs-postgres --visibility public --publish --wait --timeout 15m
What happens: AppThrust creates github.com/appthrust/<name> from
appthrust/template-nextjs (Next.js 16 App Router + pg + Dockerfile +
.github/workflows/deploy.yml), GitHub Actions builds the first
edge-<timestamp> image, the stack (web + PostgreSQL + database connection)
is placed on the shared runtime, and a publish on https://<name>.appthrust.dev/
is requested. --wait blocks until every component is Ready and the URL is
serving; on timeout it prints exactly what is still pending.
Overlap the provisioning with your own code. The stack, database, DNS and
certificate take ~3.5 min and do not depend on your source. Run the create in
the background (or omit --wait), then as soon as the repository exists
(gh repo view appthrust/<name>, ~30 s) clone it and implement; your first
push replaces the template release. Waiting for the template's first release
before writing code serializes two independent steps.
Other templates: apth application-templates list <pj> (Go, Rust, Python,
Rails, Hono… all <framework>-postgres). --port overrides the web port.
If a create fails midway (for example a GitHub name collision), rerunning the same command reuses the already-created source repository instead of failing.
B. Bring-your-own repository or image
gh repo create appthrust/<name> --template appthrust/template-nextjs --public --clone
# ...edit, commit, push main; wait for the "Docker Build and Push" run:
gh run watch --repo appthrust/<name> --exit-status
apth apps create <name> --image ghcr.io/appthrust/<name>:^edge- --publish --wait
^edge- means "follow the newest tag with this prefix" (auto-release on
every push). A fixed tag (:v1.2.0) pins the release. The GHCR package must be
pullable by AppThrust: public packages work without credentials; otherwise
create a registry connection first (apth registry-connections).
3. Develop loop
gh repo clone appthrust/<name> && cd <name>
npm ci
# implement...
npm run lint && npm run build
git commit -am "feat: ..." && git push origin main
apth apps wait <name> --released-after "$(git log -1 --format=%cI)" --timeout 10m
apth apps get <name> # Release: <tag> (Succeeded at ...), public URL
apps wait --released-after already covers the image build: it reports
release: waiting for a release created after <time> until GitHub Actions has
pushed the new tag and AppThrust has rolled it out. Do not chain
gh run watch in front of it — the Actions run keeps running (commit comment,
cleanup) after the image is pushed, and waiting for it adds minutes to every
iteration. Use gh run list/gh run view --log-failed only when apps wait
times out or the release never appears.
apps wait alone returns as soon as the current release is healthy; after a
push always pass --released-after <commit time> (or --release-version edge-<ts>) so it waits for the image built from that commit. Image: in
apps get is the stack's initial image; Release: is what is rolled out now.
Rules that keep the deploy path working:
- Keep the template
Dockerfile,deploy.yml,output: "standalone"innext.config.ts, and npm +package-lock.json. The image must listen on0.0.0.0:$PORT(3000). - The build has no network and no database. Never require
DATABASE_URLat build time; do not prerender pages that query the DB unless they handle a missing URL. - Read the repo's
AGENTS.md/CLAUDE.md; the template pins a Next.js version whose APIs may differ from your training data — checknode_modules/next/dist/docs/before writing routes or server code. - Keep secrets out of the repo and out of
NEXT_PUBLIC_*. - Keep
pg(and anything readingprocess.env) out of files imported by a"use client"component. Put shared types and constants in their own module (for examplelib/scores.ts) and importlib/db.tsonly from server components and"use server"actions; design that split before writing the first file rather than refactoring after the bundle pullspginto the browser. npm run devhas noDATABASE_URL, so a local run proves only the non-DB logic (the app must render its "not connected" state). The database path is proven on the public URL after the release; do not spend time trying to reproduce the managed database locally.
4. Database
The stack injects a managed PostgreSQL connection into the web component as
generated env vars: DATABASE_URL, DATABASE_HOST, DATABASE_PORT,
DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD, DATABASE_READ_AVAILABLE
(apth apps env get <name> lists them as "generated by connection").
Schema options:
- Demo/booth apps: create tables lazily and idempotently on first query
(
CREATE TABLE IF NOT EXISTS ...behind a once-per-process promise). - Product apps: declare a change with
apth databases changes create(seeapth databases changes --help); the template's initial schema (appthrust_demo_messages) is applied this way at create time.
Inspect data without a client: apth databases sql <pj> <database> --help,
apth databases tables, apth databases rows.
5. Configuration and logs
apth apps env get <name>
apth apps env set <name> LOG_LEVEL=debug FEATURE_X=on
apth apps env set <name> KEYCLOAK_CLIENT_SECRET="$SECRET" --sensitive KEYCLOAK_CLIENT_SECRET
apth apps env unset <name> FEATURE_X
apth apps logs <name> --tail 200
apth components restart-workload --help # when a config change needs a restart
env set merges: variables not named are preserved (sensitive values are
never re-sent or displayed). A change rolls the web workload automatically.
6. Publish and verify
--publish on create is the normal path. For an existing app:
apth apps publish <name> --wait # https://<name>.appthrust.dev/
apth apps publish <name> --subdomain play # https://play.appthrust.dev/
Verify like a user, not from status alone:
curl -sS -o /dev/null -w '%{http_code}\n' https://<name>.appthrust.dev/
curl -sS https://<name>.appthrust.dev/api/<route> | head -c 400
For an interactive UI (game, form that writes to the database), drive the
public URL once in a real browser and check the persisted result with a second
plain request. Do it in one scripted pass: for elements that move or re-render
on every event (a target that jumps on each tap, a timer-driven screen),
coordinate clicks race the re-render and time out — dispatch the DOM events
from page.evaluate instead and read the score/state back from the DOM. A
round of trial-and-error clicking costs more than the deploy itself.
Dashboard: https://dashboard.appthrust.dev/dashboard/projects/<pj>/applications/<name>/overview.
7. Actors (optional)
AppThrust Actors are per-entity stateful workers (one per match, session,
player…). On appthrust.dev they are served by the platform's actor runtime;
the runtime image is fixed, so an app uses actor methods, it does not ship
actor code. Available method sets today: Connect Four (join, drop,
state, reset), Reversi (reversi-join, reversi-place, reversi-state,
reversi-reset), and a generic counter for any other method name (each call
increments and returns a per-actor counter — enough for "one actor per
player/session" demos). New game logic needs a platform change
(pkg/actor/runtimehost in appthrust/platform), not an app change.
apth actors types list # e.g. connect-four
apth actors types create <name> --class Standard --storage KVSQL
apth actors types live connect-four # Active / Starting / Stopping / Sleeping
apth actors create connect-four match-1 --method join --body-json '{"player":"p1","name":"Reo"}'
apth actors invoke connect-four match-1 --method state
actors create|invoke require a Keycloak-issued JWT (client credentials login
above); a Dashboard device-login token is rejected by the actor gateway with
HTTP 403. The same applies inside the app: mint a client_credentials token
server-side (issuer https://iam.appthrust.dev/realms/appthrust) and call
POST {PLATFORM_API_URL}/api/v1/projects/{pj}/actor-types/{type}/actors with
{actorId, method, body} and an Idempotency-Key. Store KEYCLOAK_CLIENT_ID
/ KEYCLOAK_CLIENT_SECRET with apps env set --sensitive; the human provides
the client — the app must not create clients or widen permissions. Reference
implementation: github.com/appthrust/tgs-connect-four (lib/appthrust.ts).
Live actor visualisation: https://dashboard.appthrust.dev/dashboard/projects/<pj>/actors.
8. Cleanup
apth apps delete <name> --yes # stack, components, database, publish
gh repo delete appthrust/<name> --yes # only when the human confirms
Troubleshooting
| Symptom | Meaning / action |
|---|---|
apps wait pending: component web ... SecretNotFound ... -web-env | connection env not projected yet; normal for ~1 min after create |
component web ... ImagePullBackOff or image tag never resolves | GHCR package private or Actions build failed: gh run list --repo appthrust/<name>, package visibility |
publish pending: DNS not ready | certificate/DNS propagation, usually < 3 min; check apth publishes readiness |
HTTP 403 actor gateway rejected credential | use a client-credentials login for actor calls |
project is required | apth context set --project <pj> or --project |
placement zone is ambiguous | pass --placement <zone-id> from apth placement-zones list |
GitHub repository ... already exists | a previous attempt created it; rerun the same apps create (source is reused) or pass --source-repository <id> |
HTTP 401 on any command, or whoami reports tokenValid: false | credential expired or revoked: apth login again (the CLI prints the exact apth login --profile ... hint) |
HTTP 409 conflict (delivery_pending) | the platform's GitOps root is still syncing; the SDK already retried for ~30 s. Wait a minute and rerun the same command once |
HTTP 409 conflict (delivery_blocked) | the platform's delivery root is unhealthy; not caused by your request and retrying will not help. Tell the human to ask a platform admin |
apps wait returns immediately after a push | it saw the previous release; rerun with --released-after/--release-version |
apps wait stays on release: waiting for a release created after ... for > 5 min | the image build did not produce a tag: gh run list --repo appthrust/<name> then gh run view <id> --log-failed; fix the source and push again |
Safety
- Never print, log, or commit tokens, client secrets, or
DATABASE_URLvalues. - Mutations are idempotent-keyed by the SDK;
deleteneeds--yes. - Only touch the application you were asked about; other apps in the project belong to other demos.