All docs

Go

A static binary, a small image, and the one thing to get right about which package builds.

A go.mod, go.work or main.go at the root is enough.

marina vessels create api --repo acme/api

Your app is compiled to a single static binary with symbols stripped, and that binary is what runs. Nothing is interpreted at start, so a Go app boots about as fast as anything here, and starter is usually plenty.

The port

Marina sets PORT and routes to it. Read it, and bind all interfaces:

port := os.Getenv("PORT")
if port == "" {
	port = "3000"
}
log.Fatal(http.ListenAndServe(":"+port, nil))

":"+port binds every interface, which is what you want. "localhost:"+port does not, and produces an app that starts cleanly and answers nothing.

The Go version

From the go directive in go.mod. That is already in your repo, so there is usually nothing to add.

The thing that usually breaks

Which package gets built, when there is more than one. The root is used if it has Go files, otherwise the first directory under cmd/. A repo with several commands under cmd/ gets one of them, and it may not be the server.

Keep the server at the root, or give it the only cmd/ directory. When neither is possible, a Dockerfile is the way to say exactly what to build:

FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/server ./cmd/server

FROM gcr.io/distroless/static
COPY --from=build /out/server /server
CMD ["/server"]

A Dockerfile at the root is used automatically, and PORT is still injected.

Cgo

The default build is static, so anything requiring cgo (some SQLite drivers, some image libraries) needs a Dockerfile that sets CGO_ENABLED=1 and carries the shared libraries into the final image.