Self-Hosting
Running Manic on your own infrastructure using Bun.
Self-Hosting
Manic can be deployed anywhere that supports the Bun runtime. This is the most flexible deployment method, allowing you to run on Linux servers, Docker, or edge platforms like Fly.io.
Setup
manic build writes a .manic/ directory (by default) with server.js, the client bundle, and API bundles. Run manic start to launch the Bun server.
You do not need a deployment provider for plain self-hosting. Providers from @manicjs/providers (vercel, cloudflare, netlify) only adapt that output for specific platforms.
The listen port defaults to 6070 from config (server.port). manic start also sets the PORT environment variable for the process (CLI --port / -p overrides).
Running the Server
-
Build the application:
manic build -
Start the production server:
manic start
By default, the server listens on port 6070. Override with server.port in manic.config.ts, PORT in the environment, or manic start --port.
Docker Deployment
Create a Dockerfile:
FROM oven/bun:latest AS builder
WORKDIR /build
COPY . .
RUN bun install
RUN bun run build
FROM oven/bun:latest
WORKDIR /app
COPY --from=builder /build/.manic .manic
COPY --from=builder /build/node_modules node_modules
ENV NODE_ENV=production
ENV PORT=6070
EXPOSE 6070
CMD ["bun", ".manic/server.js"]Build and run:
docker build -t my-manic-app .
docker run -p 6070:6070 -e NODE_ENV=production my-manic-appSystemd Service
Deploy with systemd on Linux:
# /etc/systemd/system/manic.service
[Unit]
Description=Manic Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/manic
Environment="NODE_ENV=production"
Environment="PORT=6070"
ExecStart=/usr/bin/bun /var/www/manic/.manic/server.js
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl enable manic
sudo systemctl start manic
sudo systemctl status manicView logs:
sudo journalctl -u manic -fNginx Reverse Proxy
Configure Nginx to proxy requests:
upstream manic {
server localhost:6070;
}
server {
listen 80;
server_name example.com;
# Enable Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
location / {
proxy_pass http://manic;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}Enable HTTPS with Certbot:
sudo certbot --nginx -d example.comProduction Checklist
- Reverse Proxy: Use Nginx or Caddy for SSL/TLS and compression
- Process Manager: Use
systemd, Docker, or PM2 for auto-restart - Environment: Set
NODE_ENV=production - Port: Firewall configured to allow incoming traffic
- Monitoring: Set up logging and error tracking (Sentry, Grafana)
- Backups: Database and configuration backups automated
- Health Checks: Implement
/healthendpoint for uptime monitoring - Rate Limiting: Add rate limiting via Nginx or middleware
Health Check Endpoint
Add a simple health check:
// app/api/health/index.ts
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) =>
c.json({ status: 'ok', timestamp: new Date().toISOString() })
);
export default app;Monitor with a simple script:
#!/bin/bash
while true; do
curl -f http://localhost:6070/api/health || systemctl restart manic
sleep 30
donePerformance Tuning
Bun Server Options
Pass options to the manic start command (for example manic start --port 8080). The production entry point is .manic/server.js after manic build.
# Raise open-file limit on Linux before high-traffic deploys
ulimit -n 65536Database Connection Pooling
Use connection pools for databases:
// app/lib/db.ts
import { Pool } from 'pg';
export const db = new Pool({
max: 20, // Max connections
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});Static Asset Caching
Configure HTTP caching headers in API routes:
// app/api/example/index.ts
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => {
c.header('Cache-Control', 'public, max-age=3600');
return c.json({ data: 'cached' });
});
export default app;