Docker
Docker packages an Agent-Native app and everything it needs to run into a single container image. The image runs the same Node.js server described in the Node.js guide, so it behaves the same way on your laptop, a virtual machine, or any host that runs containers.
This guide walks through deploying one standalone app from scratch. You will
create the app, add a Dockerfile, give the app a production database and an
auth secret, prepare the database, and then build and run the container.
Node.js 22.22+ is required.
You also need Docker installed on the machine that builds and runs the image. Run the commands in this guide on the server that will run the container. For a virtual machine, that usually means connecting to it over SSH first.
Step 1: Create the app
Create your new standalone app. The example below creates an app from the Chat
template, but you can start from any template. Move into the app's folder and
install its dependencies. corepack enable turns on the pnpm version the app
expects, so you do not need to install pnpm yourself.
npx --yes @agent-native/core@latest create my-app --standalone --template chat
cd my-app
corepack enable
pnpm installReplace my-app with your own app name if you like. The rest of this guide
assumes you are inside the app's folder.
If these commands fail with peer dependency errors, an outdated copy of a
package in your npx cache may be the cause. Run npx clear-npx-cache to clear
the cache, then run the commands again.
Step 2: Create Dockerfile
A new app does not include a Dockerfile, so you add one yourself. You also add
a .dockerignore file that keeps local files out of the image. Create both
files in the app's root folder, next to package.json.
Dockerfile
Create a file named Dockerfile, with no file extension, and copy in the
following:
FROM node:24-slim AS build
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
ENV PYTHON=/usr/bin/python3
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:24-slim
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
COPY --from=build /app/.output .output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]The Dockerfile builds the image in two stages:
- Build stage. Installs the tools some dependencies need to compile during
install (
python3,make, andg++), installs the app's dependencies, and builds the app. - Final stage. Starts from a clean Node.js image and copies in only the
finished server from the
.outputfolder. This keeps the image small and leaves the build tools and source code behind.
The final image runs in production mode and listens on port 3000.
Docker ignore file
Create a file named .dockerignore and copy in the following:
node_modules
.output
.git
.env
.env.*
data
*.logThese entries keep local dependencies, old build output, Git history, local
secrets in .env files, the local development database in data, and log
files out of the image. The build stage installs fresh dependencies and builds
the app from scratch, so none of these are needed. Leaving out .env files also
keeps your secrets from being baked into the image.
Step 3: Configure environment secrets
The app reads its production settings from environment variables:
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
Yes | Connects the app to its PostgreSQL database |
BETTER_AUTH_SECRET |
Yes | Signs user sessions |
BETTER_AUTH_URL |
No | Sets the public address people use to reach your app |
The exports below make the following commands work in this shell. Before deploying, save these same variables in your host's project or service environment settings, so the container still has them after a restart or a new deploy.
Database URL
During local development the app stores data in a local database when
DATABASE_URL is unset. A deployed app needs a real PostgreSQL database that
keeps its data between restarts. A container loses any files it writes when it
is replaced, so the database must live outside the container. Copy the
connection string from your provider and export it:
export DATABASE_URL='your-postgres-url-string'The database provider guides show where to find the connection string for each provider.
Select a persistent PostgreSQL provider: Neon, Supabase, Amazon RDS, etc.
Auth secret
Generate this once and keep it stable across deploys.
The app uses BETTER_AUTH_SECRET to sign user sessions. If it changes, every
signed-in user is signed out. The command below generates a random value:
export BETTER_AUTH_SECRET="$(openssl rand -hex 32)"Save the generated value somewhere safe, such as your host's secret settings or a password manager. Use the same value every time you deploy.
Public URL
Optional. Set it when using a custom domain, reverse proxy, or an auth/OAuth flow that needs a fixed public origin.
BETTER_AUTH_URL is the public address people use to reach your app. Without
it, the app works out its address from each incoming request. That is usually
fine, but sign-in links and OAuth redirects can point to the wrong place when
the app sits behind a proxy or a custom domain.
export BETTER_AUTH_URL='https://your-domain.example'Step 4: Migrate
Migrations create and update the tables the app needs in your database.
Run this on the host, against the production DATABASE_URL, before the first
visit.
pnpm migrate:productionRun the command from the app's folder, not inside the container. The image only
holds the finished server, so it cannot run migrations itself. The command reads
DATABASE_URL from the environment you exported in the previous step. Run it
again after you upgrade the app, before you start the new version.
Step 5: Deploy
Build the image from the app's folder:
docker build -t my-agent-native-app .The -t flag names the image my-agent-native-app. Use any name you like, and
use the same name in the next command.
Run the container:
docker run --rm \
-p 3000:3000 \
-e DATABASE_URL \
-e BETTER_AUTH_SECRET \
my-agent-native-app-p 3000:3000 makes the container's port 3000 reachable on the server's port
3000. Each -e flag passes the variable of the same name from your shell into
the container. If you set BETTER_AUTH_URL, add -e BETTER_AUTH_URL to the
command as well. --rm removes the container when it stops.
To reach the app from the internet, open port 3000 in the server's firewall,
or put a reverse proxy such as Nginx or Caddy in front of it to serve your
domain over HTTPS. Then open the app's public address and create the first
account.
Keep the container running
The docker run command above runs in your terminal and stops when you close
it. To keep the app running, start the container in the background with a
restart policy, so Docker starts it again after a crash or a server restart.
Replace --rm with -d --restart unless-stopped to do this. Many hosts that
run containers also handle restarts for you.
What's next
- Node.js - run the same server without a container
- Deployment Environment Variables - the full list of production settings
- Deployment - compare hosting targets and prerequisites