Gothic Lady Logo GOTTH

Deployment

Deployment

The Binary

go build produces a static binary. That binary contains your entire application — server logic, templates, CSS, images, documentation. It has no external dependencies at runtime. You can copy it to any Linux amd64 machine and run it.

make build
# output: bin/server
PORT=8080 ./bin/server

The binary listens on whatever PORT says (default 3001). Bind it behind a reverse proxy (nginx, Caddy, Cloudflare Tunnel) or expose it directly. It doesn't care.

Embedded Assets

Static files live in app/assets/public/ and are compiled into the binary at build time with //go:embed:

//go:embed public/*
var Public embed.FS

The HTTP handler opens files from this embedded filesystem, not from disk. This matters on serverless platforms (Vercel, Lambda, Fly.io) where the container filesystem is read-only or laid out differently than your local machine. There is no public/ directory at runtime. There is no path to resolve. The file is in memory or it doesn't exist.

To add a new static file, drop it in app/assets/public/ and rebuild. No configuration. No CDN upload step (though you should use a CDN for production traffic — this is a local optimization, not a replacement for proper caching).

Vercel

Vercel supports Go natively. Push the repository, connect it in the dashboard, and Vercel runs go build on main.go automatically.

The vercel.json:

{
    "src": "/(.*)",
    "dest": "/main.go"
}

This rewrites all requests to the Go binary. No static file serving on Vercel's side — the binary handles everything, including static assets, from its embedded filesystem.

Important: the .templ files must be pre-compiled before pushing. Vercel does not run templ generate. The generated _templ.go files must exist in the repository. Run make setup locally, commit the generated files, and push.

Environment Variables

| Variable | Default | Description | |----------|---------|-------------| | PORT | 3001 | HTTP listen port |

That's the only variable the application reads directly. Database connection strings, API keys, S3 credentials — none of these are handled by the framework. Your handler code reads them with os.Getenv() like you would in any Go application.

Cold Starts

Go binaries start in under 10ms on modern hardware. On Vercel's serverless infrastructure, the container startup time dominates the cold start latency, not the application startup. Your users will wait roughly 200ms for the first request after a period of inactivity. This is the same as any compiled language on Vercel and significantly faster than Node.js or Python cold starts.

The go mod tidy Problem

templ generates _templ.go files outside the root module scope. If you run go mod tidy, Go's module analyzer may remove github.com/a-h/templ from go.mod because it doesn't see any direct import of the templ package — the generated files reference it, but go mod tidy checks source files, not generated output.

The fix is on line 12 of main.go:

import _ "github.com/a-h/templ"

This anonymous import locks the dependency in place. go mod tidy sees it, knows the package is used, and leaves it alone. Without this line, your next go mod tidy will delete templ, the next go build will fail, and you'll spend ten minutes figuring out why.