Deployment
Deploy your Axi application to production
Axi apps are plain Bun servers, so deploying them is just: build an image, run it, and reverse-proxy it to the internet. The scaffolded templates (bun create axi) ship a Dockerfile and docker-compose.yml to get you started.
Quick start (Docker)
If you have a Dockerfile and docker-compose.yml (from a scaffolded template, or the ones below), this is all it takes:
docker compose up -d --buildYour app is now live at http://<server>:3000.
What you get
The template ships three files:
| File | Purpose |
|---|---|
Dockerfile | Multi-stage build: install → axi build → slim runtime image |
docker-compose.yml | App service + optional Caddy sidecar for HTTPS |
Caddyfile | Reverse-proxy config used by the Caddy sidecar |
The Dockerfile is a standard Bun multi-stage build:
FROM oven/bun:1 AS build
WORKDIR /app
COPY package.json bun.lock* ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
FROM oven/bun:1 AS deps
WORKDIR /app
COPY package.json bun.lock* ./
RUN bun install --production --frozen-lockfile
FROM oven/bun:1-slim
WORKDIR /app
COPY /app/node_modules ./node_modules
COPY /app/.axi ./.axi
COPY /app/app ./app
COPY /app/public ./public
COPY /app/axi.config.ts ./axi.config.ts
EXPOSE 3000
CMD ["bun", "run", "start"]Automatic HTTPS (Caddy)
To serve your app over HTTPS with a Let's Encrypt certificate:
- Point your domain's A record at your server.
- Uncomment the
caddyservice indocker-compose.ymland setDOMAIN. - Run:
docker compose up -dCaddy terminates TLS on ports 80/443 and proxies to app:3000. It provisions and renews certificates automatically, so once DNS propagates your site is live at https://your-domain.com.
Deploying to a VPS
# 1. Install Docker on the server
curl -fsSL https://get.docker.com | sh
# 2. Get the project onto the server (rsync, git clone, or scp)
git clone https://github.com/you/my-app.git && cd my-app
# 3. Build and run
docker compose up -d --buildFor updates, git pull (or re-transfer files) then run the same command — Compose only rebuilds changed layers and restarts the container with zero manual steps.
To run multiple apps on one server, give each app a distinct host port (e.g. "3001:3000") and let a shared Caddy (or nginx) route domains to each port.
Deploying to a PaaS
Because the template includes a Dockerfile, it deploys to any container-based platform with no extra configuration:
Fly.io
fly launch # detects the Dockerfile, creates fly.toml
fly deployAxi binds to PORT from the environment, which is exactly how Fly routes traffic to your machine. Add a health check to fly.toml:
[[http_service.checks]]
path = "/api/health"
interval = "15s"Railway
railway init
railway upRailway detects the Dockerfile and builds it. Expose the app on port 3000.
Cloud Run
gcloud run deploy my-app --source . --region us-central1 --allow-unauthenticatedSet the PORT env var to 8080 if you want Cloud Run's default; otherwise Axi uses 3000.
Health checks
Add a health endpoint so load balancers and uptime monitors can check your app:
// app/api/health/route.ts
import { route } from "@axi-js/core";
export const healthCheck = route.get().handle(async () => {
return { status: "ok", timestamp: Date.now() };
});The health check is at /api/health; fall back to / if you don't define one.
Configuration
- Port: Axi reads
PORTfrom the environment first, thenaxi.config.ts, then defaults to3000. SetPORTin yourdocker-compose.ymlor platform settings. - Environment variables: pass them via
environment:in Compose,fly secrets set, or your platform's dashboard. Never commit.envfiles. - Static assets: anything in
public/is served at the root (e.g.public/robots.txt→/robots.txt).
Requirements
- Docker (locally or on the server) for the containerized flow
- Bun >= 1.3.14 for local development