Deploy go-webglue applications to production.
go-webglue applications compile to a single executable with all resources embedded:
# Build
go build -o myapp
# The binary includes:
# - Go code
# - All embedded client resources (HTML, CSS, JS)
# - No external dependencies needed# Reduce binary size
go build -ldflags="-s -w" -o myapp
# Further compression with UPX (optional)
upx --best --lzma myapp# Linux
GOOS=linux GOARCH=amd64 go build -o myapp-linux
# Windows
GOOS=windows GOARCH=amd64 go build -o myapp.exe
# macOS
GOOS=darwin GOARCH=amd64 go build -o myapp-macgo-webglue automatically minifies resources in production:
// No special configuration needed
handler, _ := webglue.NewHandler(options)
// Resources are automatically:
// - Minified (HTML, CSS, JS, JSON, SVG, XML)
// - Cached in memory
// - Served with proper MIME types# Development: Files served from disk, no minification
MYMODULE_DEV=/path/to/client go run main.go
# Production: No env var, everything embedded and minified
go build && ./myappfunc main() {
handler, err := webglue.NewHandler(options)
if err != nil {
log.Fatal(err)
}
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Starting server on port %s\n", port)
if err := http.ListenAndServe(":"+port, handler); err != nil {
log.Fatal(err)
}
}func main() {
handler, _ := webglue.NewHandler(options)
// Load certificates
certFile := os.Getenv("CERT_FILE")
keyFile := os.Getenv("KEY_FILE")
port := ":443"
log.Printf("Starting HTTPS server on %s\n", port)
err := http.ListenAndServeTLS(port, certFile, keyFile, handler)
if err != nil {
log.Fatal(err)
}
}func main() {
handler, _ := webglue.NewHandler(options)
server := &http.Server{
Addr: ":8080",
Handler: handler,
}
// Start server in goroutine
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v\n", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// Graceful shutdown with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}Dockerfile:
# Build stage
FROM golang:1.20-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy binary from builder
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]docker-compose.yml:
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- PORT=8080
restart: unless-stoppedBuild and run:
docker build -t myapp .
docker run -p 8080:8080 myappdeployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: 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"
resources:
limits:
memory: "128Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
type: LoadBalancerCreate service file /etc/systemd/system/myapp.service:
[Unit]
Description=My go-webglue Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp
Restart=on-failure
RestartSec=5s
# Environment variables
Environment="PORT=8080"
[Install]
WantedBy=multi-user.targetCommands:
# Deploy binary
sudo cp myapp /opt/myapp/
sudo chown www-data:www-data /opt/myapp/myapp
sudo chmod +x /opt/myapp/myapp
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
# Check status
sudo systemctl status myapp
# View logs
sudo journalctl -u myapp -fProcfile:
web: ./myapp
Deploy:
heroku create myapp
git push heroku main# Build and deploy
gcloud run deploy myapp \
--source . \
--platform managed \
--region us-central1 \
--allow-unauthenticated# Create application
eb init -p go myapp
# Deploy
eb create myapp-env
eb deployConfiguration:
server {
listen 80;
server_name example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# Proxy to go-webglue app
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;
}
# SSE requires special configuration
location /events {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;
}
}Caddyfile:
example.com {
reverse_proxy localhost:8080
}
Caddy automatically handles HTTPS with Let's Encrypt.
docker-compose.yml:
version: '3.8'
services:
traefik:
image: traefik:v2.9
command:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
- "--certificatesresolvers.myresolver.acme.email=admin@example.com"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
app:
build: .
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=myresolver"go-webglue apps are stateless (except API struct state), making them easy to scale:
# Docker Swarm
docker service create --replicas 5 --name myapp -p 8080:8080 myapp:latest
# Kubernetes
kubectl scale deployment myapp --replicas=5SSE connections are per-instance. To broadcast events across instances:
import "github.com/go-redis/redis/v8"
type DistributedEvents struct {
redis *redis.Client
events map[string]*webglue.Event
}
func (d *DistributedEvents) Emit(eventName string, data ...any) {
// Emit locally
d.events[eventName].Emit(data...)
// Publish to Redis
payload, _ := json.Marshal(data)
d.redis.Publish(ctx, eventName, payload)
}
func (d *DistributedEvents) Subscribe() {
pubsub := d.redis.Subscribe(ctx, "events")
for msg := range pubsub.Channel() {
var data []any
json.Unmarshal([]byte(msg.Payload), &data)
// Emit to local connections only
event := d.events[msg.Channel]
event.Emit(data...)
}
}Configure load balancer for session affinity:
nginx:
upstream myapp {
ip_hash; # Sticky sessions based on client IP
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}import "database/sql"
type MyApi struct {
db *sql.DB
}
func NewMyApi() *MyApi {
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
// Configure pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
return &MyApi{db: db}
}export DATABASE_URL="postgres://user:pass@localhost/dbname?sslmode=disable"
export REDIS_URL="redis://localhost:6379"
export SECRET_KEY="your-secret-key"type HealthApi struct{}
func (api *HealthApi) Health() string {
return "OK"
}
// Add to modules
modules := []*webglue.Module{
{
Name: "health",
Api: &HealthApi{},
},
// ... other modules
}Access at: http://your-app/api/health/health
import "log/slog"
func (api *MyApi) GetUser(id int) (*User, error) {
slog.Info("GetUser called", "userId", id)
user, err := api.db.GetUser(id)
if err != nil {
slog.Error("Failed to get user", "error", err, "userId", id)
return nil, err
}
return user, nil
}import "github.com/prometheus/client_golang/prometheus"
var (
apiCalls = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "api_calls_total",
Help: "Total number of API calls",
},
[]string{"module", "function"},
)
)
func (api *MyApi) CheckCall(req *http.Request, funcName string) ([]any, error) {
apiCalls.WithLabelValues("mymodule", funcName).Inc()
// ... auth logic
}# Never commit these!
export JWT_SECRET="your-secret-key-here"
export DATABASE_PASSWORD="secure-password"
export API_KEY="api-key"Use .env files (with godotenv package):
import "github.com/joho/godotenv"
func main() {
godotenv.Load()
secretKey := os.Getenv("JWT_SECRET")
// ...
}func withCORS(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
handler.ServeHTTP(w, r)
})
}
func main() {
handler, _ := webglue.NewHandler(options)
http.ListenAndServe(":8080", withCORS(handler))
}See authentication.md for implementation.
# Set GOMAXPROCS (usually auto-detected)
export GOMAXPROCS=4
# Tune garbage collector
export GOGC=100 # Default, lower = more frequent GCserver := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20, // 1 MB
}Resources are automatically cached in memory by go-webglue. No additional configuration needed.
# PostgreSQL
pg_dump mydb > backup.sql
# Restore
psql mydb < backup.sqlIf your API structs maintain state, implement persistence:
type MyApi struct {
counter int
mu sync.RWMutex
}
func (api *MyApi) saveState() error {
api.mu.RLock()
defer api.mu.RUnlock()
data, _ := json.Marshal(api.counter)
return os.WriteFile("state.json", data, 0644)
}
func (api *MyApi) loadState() error {
data, err := os.ReadFile("state.json")
if err != nil {
return err
}
api.mu.Lock()
defer api.mu.Unlock()
return json.Unmarshal(data, &api.counter)
}Port already in use:
# Find process using port
lsof -i :8080
# Kill process
kill -9 <PID>SSE not working behind proxy:
- Check proxy buffering is disabled
- Ensure
Connection: keep-aliveis set - Set appropriate timeouts
Large binary size:
# Strip debug info
go build -ldflags="-s -w"
# Check embedded resources
ls -lh client/
# Remove unused files- Authentication - Secure your deployment
- Examples - Complete applications
- Architecture - Understanding internals