Serve a frontend from your agent

Configure an agent that ships its own web UI instead of a chat sidecar
View as Markdown

By default, Astro AI gives your agent a built-in chat UI, Slack adapter, and OIDC-authenticated web view. If you’d rather serve your own web interface — a custom dashboard, a static SPA, a server-rendered app — set agent.interfaces.frontend: true and the platform routes incoming traffic straight to your container.

Before you start

  • An Astro AI account.
  • The ast CLI installed and authenticated.
  • An agent container you can build: a Dockerfile and an HTTP server that listens on port 80.

What frontend: true does

Declaring frontend: true changes how the agent is served in three ways:

  1. No built-in chat UI. The platform skips the chat interface that’s normally attached to your agent.
  2. Traffic routes straight to your container. A dedicated HTTPS hostname is provisioned and routes to your agent on port 80.
  3. --adapter is ignored at deploy time. Adapters (web, insecure-web, slack) only apply to messaging agents. A frontend agent serves whatever your container serves.

Default (messaging agent):

frontend: true:

You give up the built-in chat UI, but keep the OIDC sign-in at the front door. You gain full control over the request/response lifecycle.


Minimal configuration

astropods.yml
spec: blueprint/v1
name: my-dashboard
agent:
build:
context: .
dockerfile: Dockerfile
interfaces:
frontend: true
messaging: false

A few rules to know:

  • interfaces must be nested under agent. Putting it at the top level is silently ignored.
  • Omitting interfaces entirely defaults to messaging: true. As soon as you declare interfaces, messaging defaults to false — you have to opt back in if you want both.
  • When frontend: true, your container must listen on port 80 in production. This is enforced by validation rule 15 of the Astropods Spec.

Example agent

A minimal server that returns a static page. Pick your language:

server.js
import express from "express";
const app = express();
const PORT = process.env.PORT || 80;
app.get("/", (_req, res) => {
res.send(`
<!doctype html>
<html>
<head><title>My Agent</title></head>
<body>
<h1>Hello from my agent</h1>
<p>Served directly from the agent container.</p>
</body>
</html>
`);
});
app.listen(PORT, "0.0.0.0", () => {
console.log(`Listening on :${PORT}`);
});
Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 80
CMD ["node", "server.js"]

Local development on a different port

Most frameworks default to a higher port locally (Express 3000, Vite 5173, FastAPI 8000) and binding to :80 typically requires elevated privileges. Use dev.interfaces.frontend.port so ast project start runs your dev server on its native port; the platform proxies :80 to it.

astropods.yml
agent:
interfaces:
frontend: true
messaging: false
dev:
interfaces:
frontend:
port: 3000
command: bun --watch run start

With this in place, ast project start runs your container on 3000 locally and routes incoming traffic to it. In production the container still serves :80 directly — no proxy involved.

dev.interfaces.frontend.port only affects local dev. In production your container must listen on :80 — otherwise the deployed container crash-loops. If your framework defaults to a different port, set it explicitly (via PORT, a CLI flag, or your entrypoint) so the deployed container binds to :80.


Combining frontend with messaging

Setting both interfaces to true deploys your frontend container and the built-in chat interface. The frontend gets the dedicated hostname; the chat interface handles chat / Slack on its own routes.

astropods.yml
agent:
interfaces:
frontend: true
messaging: true

Use this when you want a custom UI plus the platform’s built-in Slack integration, for example.


Authentication

The platform protects a frontend agent with the same OIDC sign-in used for the built-in chat interface: by default, visitors sign in to Astro AI at the front door before any traffic reaches your container. The signed-in user’s identity is forwarded to your agent, so you can authorize requests inside the container — see Manually authorize requests.

The platform requires sign-in but does not enforce per-user access rules for a custom frontend; your own server decides what each authenticated user may do once the request arrives.


Deploy

Frontend agents deploy the same way as any other blueprint:

ast blueprint deploy my-dashboard

After deploy, ast agent list shows the assigned hostname. Open it in a browser to confirm your container is serving traffic.

If your container fails to bind to :80 in production, the deployed container crash-loops. Check ast agent logs <name> and verify the listen port matches the spec.

Open your agent from the dashboard

A deployed frontend agent has a button to open it in two places: on its card in the agents list, and on its detail page.

What you get depends on the interfaces you declared:

InterfacesButton
frontend: true onlyLaunch, which opens your agent’s URL in a new tab
messaging: true onlyLaunch, which opens the built-in chat in the dashboard
BothLaunch, with a dropdown listing both

An agent that serves both surfaces gets a Launch button with an arrow beside it. Clicking Launch opens the built-in chat. The arrow opens a menu with both destinations: Chat for the built-in chat, and Custom app for your agent’s own URL in a new tab.

Hover Launch to see which destination it opens. The button stays disabled until the agent is running, and hovering it then tells you what the agent is waiting on, such as a deploy in progress or a pause you need to resume.

Next steps