Deployment Guide
Faucet is a single static binary with no runtime dependencies. It can be deployed as a Docker container, a systemd service, or behind a reverse proxy.
Docker Deployment
Using the Official Image
docker run -d \
--name faucet \
-p 8080:8080 \
-v faucet-data:/data \
faucetdb/faucet:latestDocker Compose
version: "3.9"
services:
faucet:
image: faucetdb/faucet:latest
ports:
- "8080:8080"
volumes:
- faucet-data:/data
restart: unless-stopped
# The image has a built-in HEALTHCHECK, no need to override
volumes:
faucet-data:Building a Custom Image
The included multi-stage Dockerfile builds both the UI and the Go binary:
# Stage 1: Build UI
FROM node:22-alpine AS ui-builder
WORKDIR /app/ui
COPY ui/package*.json ./
RUN npm ci
COPY ui/ ./
RUN npm run build
# Stage 2: Build Go binary
FROM golang:1.25-alpine AS go-builder
RUN apk add --no-cache git
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
COPY --from=ui-builder /app/internal/ui/dist ./internal/ui/dist
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w -X main.version=$(git describe --tags --always 2>/dev/null || echo dev)" \
-o /faucet ./cmd/faucet
# Stage 3: Final image (alpine for shell access + minimal size)
FROM alpine:3.21
RUN apk add --no-cache ca-certificates
COPY --from=go-builder /faucet /usr/local/bin/faucet
RUN addgroup -S faucet && adduser -S faucet -G faucet
RUN mkdir -p /data && chown faucet:faucet /data
ENV FAUCET_DATA_DIR=/data
EXPOSE 8080
VOLUME /data
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --spider -q http://localhost:8080/healthz || exit 1
USER faucet
ENTRYPOINT ["faucet"]
CMD ["serve", "--foreground", "--host", "0.0.0.0"]Build it:
docker build -t faucet:custom .The final image is built FROM alpine:3.21 with a non-root faucet user, a built-in health check, and FAUCET_DATA_DIR=/data preconfigured. Data persists across restarts via the /data volume.
Kubernetes
Faucet does not ship a Helm chart. These plain manifests are a starting point.
Basic Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: faucet
labels:
app: faucet
spec:
replicas: 1
selector:
matchLabels:
app: faucet
template:
metadata:
labels:
app: faucet
spec:
containers:
- name: faucet
image: faucetdb/faucet:latest
args: ["serve", "--foreground", "--host", "0.0.0.0", "--data-dir", "/data"]
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: http
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: "1"
memory: 256Mi
env:
- name: FAUCET_AUTH_JWT_SECRET
valueFrom:
secretKeyRef:
name: faucet-secrets
key: jwt-secret
volumes:
- name: data
persistentVolumeClaim:
claimName: faucet-data
---
apiVersion: v1
kind: Service
metadata:
name: faucet
spec:
selector:
app: faucet
ports:
- port: 80
targetPort: http
name: http
type: ClusterIP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: faucet-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Secret
metadata:
name: faucet-secrets
type: Opaque
stringData:
jwt-secret: "your-production-jwt-secret-here"Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: faucet
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: faucet
port:
number: 80
tls:
- hosts:
- api.example.com
secretName: faucet-tlsImportant Notes for Kubernetes
- Faucet stores its configuration in a SQLite database. If you run multiple replicas, each needs its own PVC (or use an external config database).
- For horizontal scaling, consider running a single Faucet instance as the control plane and putting a load balancer in front of the database connections directly, or use an external PostgreSQL database for Faucet's config store (future feature).
systemd Service
Create /etc/systemd/system/faucet.service:
[Unit]
Description=Faucet REST API Server
After=network.target
[Service]
Type=simple
User=faucet
Group=faucet
ExecStart=/usr/local/bin/faucet serve --foreground --host 0.0.0.0 --port 8080 --data-dir /var/lib/faucet
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/faucet
PrivateTmp=true
# Environment
Environment=FAUCET_AUTH_JWT_SECRET=your-production-secret
[Install]
WantedBy=multi-user.targetSet up:
# Create the user and data directory
sudo useradd -r -s /bin/false faucet
sudo mkdir -p /var/lib/faucet
sudo chown faucet:faucet /var/lib/faucet
# Copy the binary
sudo cp ./bin/faucet /usr/local/bin/faucet
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable faucet
sudo systemctl start faucet
# Check status
sudo systemctl status faucet
sudo journalctl -u faucet -fReverse Proxy
nginx
upstream faucet {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
# Request size limit
client_max_body_size 10m;
location / {
proxy_pass http://faucet;
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;
# Timeouts
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# WebSocket / SSE support (for NDJSON streaming)
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
}
# Health check endpoint (no access log)
location /healthz {
proxy_pass http://faucet;
access_log off;
}
}
# HTTP redirect
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}Caddy
api.example.com {
reverse_proxy localhost:8080
# Optional: rate limiting
# rate_limit {remote.ip} 100r/m
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
X-Frame-Options DENY
}
}Caddy automatically provisions and renews TLS certificates via Let's Encrypt.
Environment Variables
All configuration can be set via environment variables with the FAUCET_ prefix. Nested keys use underscores:
| Environment Variable | Config Key | Description | Default |
|---|---|---|---|
FAUCET_SERVER_PORT | server.port | HTTP listen port | 8080 |
FAUCET_SERVER_HOST | server.host | HTTP listen host | 0.0.0.0 |
FAUCET_AUTH_JWT_SECRET | auth.jwt_secret | JWT signing secret | Random 256-bit secret, generated on first start and stored in the data directory |
Setting in Different Environments
Shell:
export FAUCET_AUTH_JWT_SECRET="your-production-secret"
export FAUCET_SERVER_PORT=9090
faucet serveDocker:
docker run -d \
-e FAUCET_AUTH_JWT_SECRET="your-production-secret" \
-e FAUCET_SERVER_PORT=8080 \
-p 8080:8080 \
faucetdb/faucet:latest serveDocker Compose:
services:
faucet:
image: faucetdb/faucet:latest
environment:
FAUCET_AUTH_JWT_SECRET: "${JWT_SECRET}"
FAUCET_SERVER_PORT: "8080"Kubernetes:
env:
- name: FAUCET_AUTH_JWT_SECRET
valueFrom:
secretKeyRef:
name: faucet-secrets
key: jwt-secretProduction Checklist
Before deploying Faucet to production, verify each item:
Security
- [ ] Decide how the JWT secret is managed. When
FAUCET_AUTH_JWT_SECRETis not set, Faucet generates a random secret on first start and stores it in the data directory, so keep that directory private and backed up. To manage it yourself (for example, to share it across replicas), setFAUCET_AUTH_JWT_SECRETto a strong random string of at least 32 characters. - [ ] Create a strong admin password. Use at least 16 characters with mixed case, numbers, and symbols.
- [ ] Use TLS. Terminate TLS at your reverse proxy (nginx, Caddy) or load balancer. Never expose Faucet over plain HTTP in production.
- [ ] Restrict network access. Bind Faucet to
127.0.0.1if it is behind a reverse proxy on the same host. - [ ] Use API keys for service consumers. Do not share admin JWT tokens with application code. Create role-scoped API keys instead.
- [ ] Review RBAC roles. Follow the principle of least privilege. Grant only the verb mask and table access each consumer needs.
- [ ] Set services to read-only where appropriate. Analytics databases and replicas should have
read_only: true. - [ ] Disable raw SQL unless needed. Only enable
raw_sql_allowedon services where AI agents or advanced users genuinely need it.
Database Connections
- [ ] Use connection strings with SSL/TLS. Set
sslmode=require(PostgreSQL),tls=true(MySQL),encrypt=true(SQL Server). - [ ] Use dedicated database users. Create a database user specifically for Faucet with the minimum required privileges.
- [ ] Tune connection pools. Adjust
max_open_connsbased on your database server's connection limits and expected traffic. - [ ] Test connections before going live. Use
faucet db test <name>to verify each service connects successfully.
Infrastructure
- [ ] Set up health checks. Point your load balancer or orchestrator at
/healthz(liveness) and/readyz(readiness). - [ ] Configure log aggregation. Faucet logs to stderr in structured text format. Forward to your log aggregation system.
- [ ] Set resource limits. In Kubernetes, set CPU and memory requests/limits. Start from the requests and limits in the manifest above and adjust them to the usage you observe.
- [ ] Back up the data directory. The SQLite config store at
~/.faucet(or--data-dir) contains all service, role, API key, and admin configurations. - [ ] Set up monitoring. Monitor the
/healthzendpoint and track API latency via your reverse proxy or APM tool.
Operational
- [ ] Pin the image version. Use a release tag such as
faucetdb/faucet:0.1.14instead of:latestin production deployments. Docker Hub tags have novprefix. - [ ] Set up automatic restarts. Use
restart: unless-stoppedin Docker Compose,Restart=on-failurein systemd, or Kubernetes restart policies. - [ ] Plan for upgrades. Faucet is a single binary. Upgrades are as simple as replacing the binary and restarting.
- [ ] Document your services. Keep a record of which database services are configured, their roles, and who holds each API key.
Graceful Shutdown
Faucet handles SIGINT and SIGTERM signals for graceful shutdown:
- Stops accepting new connections
- Drains in-flight requests (30-second timeout)
- Closes all database connections
- Removes PID file
- Exits cleanly
When running in background mode (the default), use faucet stop to trigger a graceful shutdown. When running in foreground mode (--foreground), press Ctrl+C or send SIGTERM.
This makes Faucet compatible with rolling deployments in Kubernetes and zero-downtime restarts with systemd.
Server Timeouts
The HTTP server has these timeouts:
| Timeout | Value | Description |
|---|---|---|
| Read timeout | 15 seconds | Max time to read the full request |
| Write timeout | 60 seconds | Max time to write the full response |
| Idle timeout | 120 seconds | Max time for keep-alive connections |
| Shutdown timeout | 30 seconds | Max time to drain in-flight requests |
These are suitable for most workloads. Long-running queries should use the timeout parameter in raw SQL calls rather than relying on HTTP timeouts.
Related
- Getting Started: install options and first run
- MCP Server → Built-in HTTP endpoint: the
/mcpendpoint your agents will use in production - Connect Base44, Replit and ChatGPT: expose Faucet to cloud AI tools safely
- RBAC: roles and API keys for each consumer
- Troubleshooting: common connection and API errors