Skip to content

Deployment ​

Introduction ​

vuka build makes one binary that serves your whole app. This page covers how the app runs and shuts down, the environment it reads in production, the security headers it sends, and what changes behind a proxy or with several replicas.

It ends with the router seam, for when you want another router than the standard library's.

Running the App ​

The app serves itself, or hands you an http.Handler to serve elsewhere:

app.Run(addr)builds, starts the services, serves addr until SIGINT or SIGTERM, then shuts down gracefully (10s for requests in flight) and stops the services
app.Run("")listens on :$PORT, else :8080
app.Handler()the app as an http.Handler, to serve or mount anywhere; panics when Build fails
app.Start(ctx), app.Stop(ctx)the services' lifecycle without a server, for a Handler served elsewhere
app.Build()resolves everything and returns every problem; the others call it

To serve TLS yourself, start the services, then serve the handler:

go
app := web.New()
if err := app.Start(ctx); err != nil {
	log.Fatal(err)
}
defer app.Stop(context.Background())
srv := &http.Server{Addr: ":8443", Handler: app.Handler()}
log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))

The Environment ​

The app reads these variables:

Variable
PORTthe port Run("") listens on
VUKA_ENVwhich env files are read; development shows internal error messages (as web.Dev() does) and logs the files read
VUKA_CHROMEthe Chrome binary for browser tests

In production, set real environment variables and leave VUKA_ENV unset (or production): internal errors answer "Internal Server Error", never their message.

VUKA_ENV and the other VUKA_ variables are read from the real environment only; an env file can't set them. A binary started outside a Go module reads env files from its working directory alone, never from a directory above it.

WARNING

Build with Go 1.27.2 or newer: earlier releases have standard-library security fixes (net/http, crypto/tls and others) the binary doesn't get otherwise.

Security Headers ​

Every response carries X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, X-Frame-Options: DENY and Content-Security-Policy: frame-ancestors 'none', and HTTPS responses Strict-Transport-Security: max-age=31536000, unless the handler sets its own.

These options change them:

Option
web.Headers(web.SecurityHeaders{…})change them: FrameOptions: "SAMEORIGIN" (the CSP follows), ReferrerPolicy, ContentTypeOptions, CSP, HSTS (a max-age), HSTSSubdomains; "-" (or a negative HSTS) leaves one out
web.NoSecurityHeaders()none: a proxy in front sets them
web.MaxRequestBody(n)the largest request body, 32 MiB by default (uploads)

Behind a Proxy ​

A proxy or load balancer in front of the app makes every request come from its address. Name it, and only then are its headers believed:

go
app := web.New(web.TrustedProxies("10.0.0.0/8", "fd00::/8"))

From those addresses, web.ClientIP(r) is the nearest address in X-Forwarded-For (else Forwarded) that isn't a trusted proxy; entries a client added itself further left are ignored. X-Forwarded-Proto: https makes a request HTTPS (web.IsHTTPS(r)) for HSTS.

From any other address, those headers are ignored and web.ClientIP(r) is the connection's peer. Login throttling and the live connection caps per client address count by it.

Building One Binary ​

vuka build makes one binary: views and every vuka.File are embedded, and so is the live runtime. Static files are served from an embed.FS:

go
//go:embed static
var static embed.FS

func main() {
	app := web.New()
	assets, _ := fs.Sub(static, "static")
	app.Static("/assets", assets)
	log.Fatal(app.Run(""))
}

Running Several Replicas ​

Several replicas behind one hostname need a shared web.LiveKey(key) for live pages, and a web.WithRelay(r) for broadcasts to reach every replica.

Using Another Router ​

The default router is net/http's ServeMux with Go 1.22 patterns (web.NewServeMux()). Any router plugs in through this interface:

go
type Router interface {
	http.Handler
	Handle(method, pattern string, h http.Handler) // canonical {name} / {name...} patterns
	Param(r *http.Request, name string) string
}

Pass yours with web.WithRouter:

go
app := web.New(web.WithRouter(myRouter))

A chi adapter is a dozen lines: Handle → r.Method(method, pattern, h), Param → chi.URLParam(r, name). Paths are always written {name} and {name...}; an adapter translates them if its router spells them otherwise.

App-wide middleware (web.Use) wraps the router, so it runs for every request, 404s included.

Released under the MIT License. Vuka builds on Go and its tools, which are made by the Go Authors. Vuka is not affiliated with Google or the Go project. Go is a trademark of Google LLC.