Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
934347b
feat(self-updating-mastra): scaffold two-service compose skeleton
raphaeltm Jul 16, 2026
9f430ba
feat(self-updating-mastra): Mastra coding-agent server + dev containe…
raphaeltm Jul 16, 2026
7d16109
wip(self-updating-mastra): todo-app snapshot (agent build in progress)
raphaeltm Jul 16, 2026
e9ed6c7
feat(self-updating-mastra): build todo feedback workflow
raphaeltm Jul 16, 2026
942583e
docs(self-updating-mastra): add usage and safety guide
raphaeltm Jul 16, 2026
dfb9967
Merge remote-tracking branch 'origin/main' into feature/self-updating…
raphaeltm Jul 16, 2026
164f9d8
fix(self-updating-mastra): use sslmode=no-verify for Cloud SQL compat…
raphaeltm Jul 17, 2026
812046f
fix(self-updating-mastra): unblock login behind LB and serialize sche…
raphaeltm Jul 17, 2026
00be415
fix(self-updating-mastra): use real Vertex model gemini-3-flash; fail…
raphaeltm Jul 17, 2026
9614ead
fix(self-updating-mastra): use gemini-2.5-flash (gemini-3-flash not s…
raphaeltm Jul 17, 2026
44c5741
feat(self-updating-mastra): move admin console into the Hono agent se…
raphaeltm Jul 17, 2026
ded1942
feat(self-updating-mastra): run history, error backlog capture, auth …
raphaeltm Jul 17, 2026
40c77a1
feat(self-updating-mastra): git-backed run history, admin revert, on-…
raphaeltm Jul 17, 2026
b894cd1
feat(self-updating-mastra): admin-triggered self-redeploy with per-pu…
raphaeltm Jul 17, 2026
7479a15
fix(self-updating-mastra): cancel-vs-child-exit race marked cancelled…
raphaeltm Jul 17, 2026
83ab507
fix(self-updating-mastra): admin console script had raw newlines and …
raphaeltm Jul 18, 2026
109c89f
feat(self-updating-mastra): make deployment logs durable and viewable…
raphaeltm Jul 18, 2026
5384da3
fix(self-updating-mastra): install nightly Defang CLI for keyless GCP…
raphaeltm Jul 18, 2026
faa7073
fix(self-updating-mastra): grant secretmanager.viewer for publish con…
raphaeltm Jul 18, 2026
adc3082
fix(self-updating-mastra): address PR #648 review
defang-sam[bot] Jul 18, 2026
b4f302c
refactor(self-updating-mastra): worktree isolation, model portability…
defang-sam[bot] Jul 18, 2026
bc32459
fix(self-updating-mastra): grant dns.reader for publish delegate-zone…
raphaeltm Jul 18, 2026
2f2512c
feat(self-updating-mastra): history offers per-commit undo and reset-…
raphaeltm Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy-changed-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ jobs:
TEST_ANTHROPIC_API_KEY: ${{ secrets.TEST_ANTHROPIC_API_KEY }}
TEST_AWS_ACCESS_KEY: ${{ secrets.TEST_AWS_ACCESS_KEY }}
TEST_AWS_SECRET_KEY: ${{ secrets.TEST_AWS_SECRET_KEY }}
TEST_BETTER_AUTH_SECRET: ${{ secrets.TEST_SESSION_SECRET }}
TEST_BOARD_PASSWORD: ${{ secrets.TEST_BOARD_PASSWORD }}
TEST_DATABASE_HOST: ${{ secrets.TEST_DATABASE_HOST }}
TEST_DATABASE_NAME: ${{ secrets.TEST_DATABASE_NAME }}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ When you add a new sample, make sure to add any config vals to the `deploy-chang
| [Rocket](./samples/rocket) | A simple Rocket app. | Rocket | Rust |
| [Sails.js](./samples/sailsjs) | A short hello world application demonstrating how to deploy Sails.js onto Defang. | Sails.js, Node.js | nodejs |
| [Sails.js & PostgreSQL](./samples/sailsjs-postgres) | A sample project demonstrating how to deploy a project with PostgreSQL and Sails.js. | PostgreSQL, Sails.js, SQL, JavaScript | nodejs |
| [Self-updating Mastra Todo](./samples/self-updating-mastra) | A Next.js todo app where an admin can turn stored user feedback into live code changes with a Mastra coding agent. | Mastra, Next.js, PostgreSQL, Better Auth, AI, Agents | TypeScript, JavaScript, Docker |
| [Svelte & Node.js & MySQL](./samples/svelte-mysql) | A full-stack application using Svelte for the frontend, Node.js for the backend, and MySQL for the database. | Svelte, Node.js, MySQL, Full-stack, JavaScript, TypeScript, SQL | nodejs |
| [SvelteKit](./samples/sveltekit) | A minimal SvelteKit app running on Defang. | SvelteKit, TypeScript, JavaScript, Svelte, Node.js, Frontend, TypeScript, JavaScript | nodejs |
| [SvelteKit & MongoDB](./samples/sveltekit-mongodb) | A full-stack application using SvelteKit for the frontend and MongoDB for the database. | SvelteKit, MongoDB, Full-stack, Node.js, JavaScript | nodejs |
Expand Down
2 changes: 2 additions & 0 deletions samples/self-updating-mastra/.defang/aws
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
AWS_REGION="us-east-1"
DEFANG_PROVIDER="aws"
9 changes: 9 additions & 0 deletions samples/self-updating-mastra/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Build context for Dockerfile.dev (and, for the self-redeploy, the dev
# container's own filesystem becomes the build context, which must stay
# under Defang's 100 MiB limit). NOTE: .git is deliberately NOT ignored —
# the in-container git history (runs, publishes) must travel into the next
# dev image so the admin console's history survives self-redeploys. The
# heavy directories are gitignored, so .git stays small.
**/node_modules
**/.next
README.md
10 changes: 10 additions & 0 deletions samples/self-updating-mastra/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Applies to the in-container repo created by entrypoint.dev.sh (and to this
# directory when nested in a samples checkout). Keeps the runtime git history
# small so it can ride along in the self-redeploy build context.
.defang/
*.log

# The coding agent's isolated worktree, if ever created inside this tree. It
# normally lives outside /workspace (see paths.ts), but guard against it landing
# in the served tree or the self-redeploy build context.
agent-worktree/
34 changes: 34 additions & 0 deletions samples/self-updating-mastra/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Public entrypoint for the dev service. Routes the admin console to the coding
# agent server (:4111) and everything else to the Next.js dev server (:3001).
# The two run as independent processes, so the admin console — hosted by the
# agent server, outside the app the agent edits — stays reachable even when a
# bad edit crashes Next.js. Caddy keeps retrying while a backend restarts, so
# users see a brief delay instead of an error page.
{
admin off
auto_https off
}

:3000 {
@admin path /admin /admin/*
handle @admin {
reverse_proxy localhost:4111 {
lb_try_duration 30s
lb_try_interval 250ms
}
}
handle {
reverse_proxy localhost:3001 {
lb_try_duration 30s
lb_try_interval 250ms
}
}
}

# Listener for the second (host-mode) port. Its real job is to exist: a second
# port forces this service onto a Compute Engine VM instead of Cloud Run, so
# live edits survive idle periods (no scale-to-zero).
:8081 {
respond /healthz "ok" 200
respond "ok" 200
}
45 changes: 45 additions & 0 deletions samples/self-updating-mastra/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Image for the live "dev environment" service. Unlike the production app
# image, this one carries the FULL sample source tree — the coding agent edits
# it in place and the Next.js dev server hot-reloads the changes.
FROM node:22-bookworm-slim

RUN apt-get update \
&& apt-get install -y --no-install-recommends git curl ca-certificates debian-keyring debian-archive-keyring apt-transport-https gnupg \
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg \
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends caddy \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /workspace

# Defang CLI for the admin-triggered self-redeploy ("publish"). Installed from
# the latest NIGHTLY, resolved at build time: the publish flow needs the GCE
# metadata-server principal fallback (DefangLabs/defang#2174) which no stable
# release carries yet, and nightly release assets are replaced daily, so a
# pinned nightly asset URL would 404 on the next self-rebuild. TODO: re-pin to
# the first stable release containing #2174 (restore ARG DEFANG_VERSION + the
# v-tag URL). Builds happen remotely in the CD task, so no Docker daemon is
# needed here. Fabric auth is a per-publish interactive login (no token baked
# in); GCP auth is the instance service account via the metadata server (roles
# granted in compose.yaml via x-defang-roles).
ARG TARGETARCH=amd64
RUN url=$(curl -fsSL https://api.github.com/repos/DefangLabs/defang/releases/tags/nightly \
| grep -o "https://[^\"]*linux_${TARGETARCH}\.tar\.gz" | head -1) \
&& echo "installing defang CLI from $url" \
&& curl -fsSL "$url" | tar -xz -C /usr/local/bin defang \
&& defang --version

# Install dependencies first for better layer caching.
COPY todo-app/package.json todo-app/package-lock.json ./todo-app/
RUN cd todo-app && npm ci

COPY agent/package.json agent/package-lock.json ./agent/
RUN cd agent && npm ci

# The rest of the source tree — this is what the agent edits at runtime.
COPY . .

EXPOSE 3000 8081

CMD ["./entrypoint.dev.sh"]
186 changes: 186 additions & 0 deletions samples/self-updating-mastra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Self-updating Mastra Todo

[![1-click-deploy](https://raw.githubusercontent.com/DefangLabs/defang-assets/main/Logos/Buttons/SVG/deploy-with-defang.svg)](https://portal.defang.dev/redirect?url=https%3A%2F%2Fgithub.com%2Fnew%3Ftemplate_name%3Dsample-self-updating-mastra-template%26template_owner%3DDefangSamples)

This sample is a multi-user Next.js todo app that turns user feedback into live
code changes. Better Auth and PostgreSQL provide accounts, private todo lists,
and feedback storage. The first person to sign up becomes the administrator.
From the admin page, that person can curate feedback, add instructions, and send
the request to a Mastra coding agent that edits the app source, which the Next.js
development server then hot-reloads.

To keep those edits from being served half-applied, the coding agent works in an
**isolated git worktree**, not the files the dev server is serving. It makes and
typechecks all its edits there; only a successful, compiling run is
fast-forwarded into the live tree in a single step, so users see one atomic
update instead of every intermediate keystroke (a multi-file change would
otherwise flash broken states and flood the backlog with transient errors).

The Compose project separates the public production app from the live-editing
environment:

- `app` is a standalone Next.js production build with no source, agent, or admin UI.
- `dev` contains the source, Caddy, the Next.js dev server, and the Mastra agent.
- `db` is shared PostgreSQL for auth, todos, and feedback.
- `chat` is the managed model used by the coding agent. It is declared with the
Docker Compose `models:` syntax as `ai/chat-default`, so Defang maps it to the
cloud's native inference — Gemini on GCP Vertex AI, Claude on AWS Bedrock.

The **admin console is served by the agent server, not the Next.js app** — it
lives outside the source tree the coding agent edits. Caddy routes `/admin` to
the agent server and everything else to Next.js, so the console stays usable to
recover the app even if a bad edit crashes the Next.js dev server.

> [!WARNING]
> This is a demo of agent-driven development, not a production software-update
> design. The coding agent can change any file under `todo-app/`. The admin gate,
> source boundary, and compile check reduce risk, but they are not a substitute
> for code review, tests, signed artifacts, or a deployment approval process.

## Prerequisites

1. Download the [Defang CLI](https://github.com/DefangLabs/defang).
2. Authenticate with a cloud account that has managed LLMs: a GCP project with
Vertex AI, or an AWS account with Bedrock model access.
3. For local development, install Docker Desktop with
[Docker Model Runner](https://docs.docker.com/ai/model-runner/) enabled.

## Development

Start the live-editing environment, PostgreSQL, and the local model:

```bash
docker compose -f compose.dev.yaml up --build
```

Then open `http://localhost:3000` and:

1. Sign up. The first account becomes the administrator.
2. Add a few todos.
3. Use the feedback button in the lower-right corner.
4. Open **Admin**, select feedback, add instructions, and choose
**Send to coding agent**.
5. Watch the run log. Successful edits appear in `todo-app/` and hot-reload in
the browser.

The local model is `ai/qwen2.5-coder:7B-Q4_K_M`. It is intentionally small so
the workflow can run on a laptop; use local development to test the loop, not to
judge the quality of the generated changes. The first run downloads the model.

## Configuration

Set these secrets before deploying:

```bash
defang config set POSTGRES_PASSWORD --random
defang config set BETTER_AUTH_SECRET --random
defang config set ADMIN_TOKEN --random
```

- `POSTGRES_PASSWORD` protects the managed PostgreSQL database.
- `BETTER_AUTH_SECRET` signs and encrypts authentication data. Keep it stable
across deployments or existing sessions will be invalidated.
- `ADMIN_TOKEN` is a break-glass password for the admin console. The console
normally accepts your regular admin login (its session is validated directly
against PostgreSQL, so it works even while the app is down); the token is the
fallback for when no valid session is available.

## Deployment

The `chat` model is `ai/chat-default`, so the same Compose project deploys to
either cloud — Defang resolves it to that provider's managed inference. The
first deployment creates managed PostgreSQL and can take about 20 minutes.
Defang reports separate URLs for `app` and `dev`:

- Share the `app` URL with normal users.
- Use the `dev` URL for administration and live agent changes.

The `dev` service deliberately runs as one always-on instance (it declares two
ports, which keeps it off the serverless path) so its working tree survives idle
periods. The `app` service remains a stateless production build.

### GCP (Vertex AI)

`ai/chat-default` resolves to Gemini on Vertex AI. Select a GCP project and a
region where managed LLMs are available; `europe-west2` is the configuration
used for this sample's London demo.

```bash
export DEFANG_PROVIDER=gcp
export GCP_PROJECT_ID=your-gcp-project
export GCP_LOCATION=europe-west2

defang compose up
```

### AWS (Bedrock)

`ai/chat-default` resolves to a Claude model on Bedrock. Enable model access for
it in the Bedrock console first. A committed stack file (`.defang/aws`, region
`us-east-1`) makes AWS an explicit deploy target:

```bash
defang compose up --stack aws
```

> [!NOTE]
> `PUBLISH_STACK` in `compose.yaml` selects which stack the in-container
> **Publish** button self-redeploys to (`beta` by default). To make AWS the
> self-redeploy target, set `PUBLISH_STACK: aws`.

## Publishing (self-redeploy)

The admin console's **Publish** panel promotes the live, agent-edited workspace
into a new production build — by having the dev container run
`defang compose up` on its own Compose project, overwriting **both** the `dev`
and `app` services. There is deliberately no GitOps pipeline: the deployed app
mutates its own deployment.

- **Authorization is per publish.** Clicking Publish starts an interactive
`defang login` inside the container; the console surfaces the login URL in a
new tab and the admin must complete it for every deployment. No Defang token
is stored anywhere. After login, the panel shows who you are signed in as —
make sure it is the tenant that owns this stack — before the final
"Deploy and overwrite" button.
- **Cloud credentials are ambient, not baked.** The `dev` service's own
service account is granted the deploy roles in `compose.yaml` via
`x-defang-roles`, and the CLI picks it up from the metadata server.
- **History survives.** The workspace's git history (one commit per agent run,
one per publish, each referencing the database rows it addressed) rides
along in the build context, so the next dev container continues the same
lineage.
- `PUBLISH_STACK` in `compose.yaml` must match the stack the project was
deployed with; publishing is disabled in local development.

## Run history and revert

Every successful agent run is committed in the agent worktree and fast-forwarded
into the live tree with trailers (`Run-Id`, `Feedback-Id`, `Model`) linking it to
the run and feedback rows in Postgres; a failed run is discarded in the worktree
and never reaches the live app. The
admin console's **History** panel lists the lineage, links agent commits to
their run logs, and offers an admin-only revert (a new commit authored as the
admin). Runs can be graded on demand ("Grade this run") with a second model
call.

## Safety boundaries

- The Mastra Workspace filesystem is rooted at `todo-app/`; it cannot edit its
own agent server.
- The agent listens only on `127.0.0.1` inside `dev`.
- Next.js server routes validate the Better Auth session and admin role before
dispatching or reading agent runs.
- Every run ends with `tsc --noEmit`. A failed check triggers one repair attempt
and is reported as failed if the app still does not compile.
- The production `app` image contains neither the coding agent nor application
source files.

---

Title: Self-updating Mastra Todo

Short Description: A Next.js todo app where an admin can turn stored user feedback into live code changes with a Mastra coding agent.

Tags: Mastra, Next.js, PostgreSQL, Better Auth, AI, Agents

Languages: TypeScript, JavaScript, Docker
1 change: 1 addition & 0 deletions samples/self-updating-mastra/agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
Loading
Loading