| name | lvt-deploy |
| description | Use when deploying LiveTemplate applications to production - covers Docker containerization, Fly.io deployment, Kubernetes setup, database persistence, and production best practices |
lvt:deploy
Deploy LiveTemplate applications to production environments.
🎯 ACTIVATION RULES
Context Detection
This skill typically runs in existing LiveTemplate projects (.lvtrc exists).
✅ Context Established By:
- Project context -
.lvtrc exists (most common scenario)
- Agent context - User is working with
lvt-assistant agent
- Keyword context - User mentions "lvt", "livetemplate", or "lt"
Keyword matching (case-insensitive): lvt, livetemplate, lt
Trigger Patterns
With Context:
✅ Generic prompts related to this skill's purpose
Without Context (needs keywords):
✅ Must mention "lvt", "livetemplate", or "lt"
❌ Generic requests without keywords
Overview
LiveTemplate apps are standard Go binaries with SQLite databases, making them easy to deploy anywhere Go runs. This skill covers:
- Docker - Containerize for any platform
- Fly.io - Optimized for SQLite apps with built-in persistence
- Kubernetes - For large-scale deployments
- Traditional VPS - Simple binary deployment
Prerequisites
Before deployment:
- ✓ App builds successfully (
go build ./cmd/myapp)
- ✓ All migrations applied (
lvt migration status)
- ✓ Tests pass (
go test ./...)
- ✓ Production database prepared
- ✓ Environment variables configured
Docker Deployment
Important: CGO is NOT required
LiveTemplate apps use modernc.org/sqlite, a pure Go SQLite implementation. This means:
CGO_ENABLED=0 works perfectly — no C compiler needed
- No
gcc, musl-dev, or sqlite-dev packages required
- Cross-compilation works without special tooling
- Docker images are smaller and faster to build
1. Generate Docker Stack
Use the built-in generator to create optimized Docker deployment files:
lvt gen stack docker
lvt gen stack docker --db postgres
lvt gen stack docker --backup litestream --storage s3
lvt gen stack docker --db postgres --redis upstash --ci github
The generator creates:
deploy/Dockerfile — optimized multi-stage build with CGO_ENABLED=0
deploy/.dockerignore — excludes build artifacts and sensitive files
deploy/.env.example — environment variable template
deploy/README.md — deployment documentation
Makefile — convenient build/run/stop commands
deploy/docker-compose.yml — only when multiple services are needed (postgres, litestream, redis)
2. Build and Run (Simple — SQLite)
make build
make run
make logs
make stop
make backup
3. Build and Run (Complex — Postgres/Litestream)
cd deploy && docker compose up -d
cd deploy && docker compose logs -f app
cd deploy && docker compose down
4. Docker Compose
Docker Compose is only generated when your stack needs multiple services:
- PostgreSQL database
- Litestream backup
- Redis
For simple SQLite apps, use the Makefile targets instead.
Fly.io Deployment
Fly.io is ideal for SQLite apps with built-in persistence and global distribution.
1. Install flyctl
brew install flyctl
curl -L https://fly.io/install.sh | sh
fly auth login
2. Create fly.toml
app = "myapp"
primary_region = "sjc"
[build]
builder = "paketobuildpacks/builder:base"
[env]
PORT = "8080"
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
[mounts]
source = "myapp_data"
destination = "/data"
3. Deploy to Fly.io
fly launch
fly volumes create myapp_data --region sjc --size 1
fly deploy
fly status
fly logs
fly open
fly ssh console
4. Run Migrations on Fly.io
lvt migration up
fly deploy
fly ssh console
goose -dir database/migrations sqlite3 /data/app.db up
[deploy]
release_command = "goose -dir database/migrations sqlite3 /data/app.db up"
5. Scale on Fly.io
fly regions add iad lhr syd
fly scale vm shared-cpu-1x --memory 512
fly scale count 2
fly autoscale set min=1 max=5
Kubernetes Deployment
For large-scale production deployments.
1. Create Kubernetes Manifests
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
- name: DATABASE_PATH
value: "/data/app.db"
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: myapp-pvc
service.yaml:
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
pvc.yaml (for SQLite persistence):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
2. Deploy to Kubernetes
kubectl apply -f pvc.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl get pods
kubectl get services
kubectl logs -f deployment/myapp
kubectl scale deployment myapp --replicas=5
kubectl set image deployment/myapp myapp=myapp:v2
3. Important: SQLite + Kubernetes
Warning: SQLite doesn't support concurrent writes across multiple pods. For K8s:
Option A: Single replica (simple)
spec:
replicas: 1
Option B: Read replicas (advanced)
Option C: Switch to PostgreSQL
Traditional VPS Deployment
Simple deployment to DigitalOcean, Linode, AWS EC2, etc.
1. Build Binary
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myapp cmd/myapp/main.go
ssh user@server
cd /opt/myapp
go build -o myapp cmd/myapp/main.go
2. Create systemd Service
[Unit]
Description=MyApp LiveTemplate Application
After=network.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp
Restart=on-failure
RestartSec=5s
Environment="PORT=8080"
Environment="DATABASE_PATH=/var/lib/myapp/app.db"
Environment="ENV=production"
[Install]
WantedBy=multi-user.target
3. Deploy Steps
scp -r . user@server:/opt/myapp/
ssh user@server
sudo useradd -r -s /bin/false myapp
sudo mkdir -p /var/lib/myapp
sudo chown myapp:myapp /var/lib/myapp
cd /opt/myapp
go mod download
go build -o myapp cmd/myapp/main.go
lvt migration up
go install github.com/pressly/goose/v3/cmd/goose@latest
goose -dir database/migrations sqlite3 /var/lib/myapp/app.db up
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myapp
sudo journalctl -u myapp -f
4. Nginx Reverse Proxy
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.com;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
}
}
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d myapp.com
Production Considerations
1. Database Backups
SQLite backups:
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/myapp"
DB_PATH="/var/lib/myapp/app.db"
mkdir -p $BACKUP_DIR
sqlite3 $DB_PATH ".backup '$BACKUP_DIR/app_$DATE.db'"
find $BACKUP_DIR -name "app_*.db" -mtime +7 -delete
echo "Backup completed: app_$DATE.db"
Cron job:
0 2 * * * /opt/myapp/backup.sh
Fly.io backup:
fly volumes snapshots create myapp_data
fly volumes snapshots list myapp_data
fly volumes restore myapp_data <snapshot-id>
2. Environment Variables
Create .env file (never commit to git):
PORT=8080
DATABASE_PATH=/var/lib/myapp/app.db
ENV=production
SECRET_KEY=your-secret-key-here
Load in app:
package main
import (
"os"
"github.com/joho/godotenv"
)
func main() {
if os.Getenv("ENV") != "production" {
godotenv.Load()
}
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
}
3. Migrations in Production
IMPORTANT: LiveTemplate migrations are run with lvt migration up (development) or goose (production).
Before deployment - Run migrations in development:
cd /path/to/app
lvt migration status
lvt migration up
go build ./cmd/myapp
Option A: Include goose in Docker image:
# Add to Dockerfile before CMD
RUN go install github.com/pressly/goose/v3/cmd/goose@latest
# Run migrations on container start
CMD goose -dir database/migrations sqlite3 /data/app.db up && ./myapp
Option B: Run migrations manually before deploy:
docker run --rm \
-v $(pwd)/data:/data \
myapp:latest \
goose -dir database/migrations sqlite3 /data/app.db up
fly ssh console
goose -dir database/migrations sqlite3 /data/app.db up
ssh user@server
cd /opt/myapp
lvt migration up
goose -dir database/migrations sqlite3 /var/lib/myapp/app.db up
sudo systemctl restart myapp
Option C: Auto-migrate on deploy (advanced):
import (
"database/sql"
"github.com/pressly/goose/v3"
)
func main() {
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
log.Fatal(err)
}
if err := goose.Up(db, "database/migrations"); err != nil {
log.Fatalf("Migration failed: %v", err)
}
}
4. Monitoring and Logs
Structured logging:
import "log/slog"
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("Server starting", "port", port)
logger.Error("Database error", "error", err)
Health check endpoints (already included in generated apps):
http.HandleFunc("/health/live", healthLiveHandler)
http.HandleFunc("/health/ready", healthReadyHandler)
Configure Docker/K8s health checks to use /health/live for liveness and /health/ready for readiness probes.
5. Performance Tuning
SQLite optimizations:
db.Exec("PRAGMA journal_mode=WAL")
db.Exec("PRAGMA synchronous=NORMAL")
db.Exec("PRAGMA cache_size=-64000")
db.Exec("PRAGMA temp_store=MEMORY")
6. Static Files and Templates
LiveTemplate serves:
- Client library:
livetemplate-client.browser.js
- Template files:
app/**/*.tmpl
- Static assets: CSS, images, etc.
Option A: Embed in binary (recommended):
import "embed"
var templates embed.FS
var static embed.FS
var client embed.FS
func main() {
}
Option B: Copy files in Docker:
# Already handled by the generated Dockerfile
COPY --from=builder /app/app ./app
Verify static files are served:
curl http://localhost:8080/static/livetemplate-client.browser.js
Common Deployment Mistakes
❌ Not Persisting Database
# WRONG - database lost on container restart
FROM alpine
COPY myapp .
CMD ["./myapp"]
# CORRECT - mount volume for persistence
FROM alpine
COPY myapp .
VOLUME ["/data"]
ENV DATABASE_PATH=/data/app.db
CMD ["./myapp"]
Why wrong: Without volume, database is lost when container stops.
❌ Forgetting Migrations in Production
git push production main
ssh production
./myapp migrate up
sudo systemctl restart myapp
Why wrong: Production database is out of sync with code.
❌ Multiple SQLite Writers in K8s
spec:
replicas: 5
spec:
replicas: 1
Why wrong: SQLite doesn't support concurrent writes from multiple processes.
❌ Hardcoded Paths and Ports
db, err := sql.Open("sqlite3", "/Users/me/myapp/app.db")
server.ListenAndServe(":8080", nil)
dbPath := os.Getenv("DATABASE_PATH")
if dbPath == "" {
dbPath = "./app.db"
}
db, err := sql.Open("sqlite3", dbPath)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
server.ListenAndServe(":" + port, nil)
Why wrong: Environment-specific paths break in production.
Deployment Checklist
Before deploying:
After deploying:
Quick Reference
| I want to... | Best Option | Command |
|---|
| Generate Docker stack | lvt CLI | lvt gen stack docker |
| Build image | Makefile | make build |
| Run container | Makefile | make run |
| Deploy quickly | Fly.io | fly launch && fly deploy |
| Use existing VPS | systemd | systemctl enable myapp |
| Auto-scale | Fly.io or K8s | fly autoscale set min=1 max=5 |
| Global distribution | Fly.io | fly regions add iad lhr |
| Multi-service (postgres) | Docker Compose | cd deploy && docker compose up |
| Backup database | Makefile or Cron | make backup or sqlite3 app.db .backup |
| Run migrations | goose | goose -dir database/migrations sqlite3 app.db up |
| Cross-compile | go build | CGO_ENABLED=0 GOOS=linux go build ./cmd/myapp |
Recommended: Fly.io for SQLite Apps
For most LiveTemplate apps, Fly.io is the best choice:
✓ Built-in SQLite persistence (volumes)
✓ Global distribution (multi-region)
✓ Auto-scaling
✓ Zero-downtime deploys
✓ Built-in SSL
✓ Easy rollbacks
✓ Affordable ($0-5/month for small apps)
fly launch
fly volumes create myapp_data --size 1
fly deploy
fly open
Remember
✓ LiveTemplate uses modernc.org/sqlite — pure Go, no CGO needed
✓ Use lvt gen stack docker to generate optimized deployment files
✓ Use make build / make run for simple deployments
✓ Use volumes/mounts for database persistence
✓ Run migrations with lvt migration up (dev) or goose (production)
✓ Configure environment variables (never hardcode)
✓ Set up automated backups
✓ Add health check endpoint
✓ Test Docker builds locally before deploying
✓ SQLite = single writer (use replicas=1 in K8s)
✓ Embed or copy static files/templates/client library
✓ Cross-compilation works without special tooling (CGO_ENABLED=0)
✗ Don't deploy without testing build
✗ Don't forget database persistence
✗ Don't skip migrations in production
✗ Don't assume ./myapp migrate up exists (use lvt or goose)
✗ Don't use multiple SQLite writers in K8s
✗ Don't hardcode paths or ports
✗ Don't commit .env files or secrets
✗ Don't deploy without backup strategy