Go on Previewops
Previewops builds whatever your Dockerfile produces and runs it as a container, so Go needs no special support. What it does need is what every stack needs: read the injected PORT and listen on 0.0.0.0.
| Read the port | os.Getenv("PORT") |
| Bind all interfaces | http.ListenAndServe(":"+port, nil) |
Dockerfile
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY . .
RUN go build -o /out/server ./cmd/server
FROM alpine:3.20
COPY --from=build /out/server /server
ENV PORT=8080
CMD ["/server"]
ENV PORT=8080 is only a default. Previewops overrides it at deploy time, so the app must read the value rather than assume it.
Application code
A typical setup with net/http:
package main
import (
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
// ":"+port binds every interface. "localhost:"+port would not.
log.Fatal(http.ListenAndServe(":"+port, nil))
}
Go-specific pitfalls
localhost:8080binds loopback only. Use":"+portwith no host so the listener accepts proxied connections.- CGO on Alpine. If your build links C libraries, either set
CGO_ENABLED=0or use a Debian-based runtime image — a CGO binary will not start on plain Alpine. scratchimages have no CA certificates. If your app makes outbound HTTPS calls, copy/etc/ssl/certsin, or usealpineor distroless instead.
Configuration
If your Dockerfile is not at the repository root, point at it from .previewops.yaml:
provider: cloud-run
dockerfile: ./docker/Dockerfile
port: 8080
ttl_hours: 24
See configuration.md for every available option.
Do not gate startup behind a migration
Language-agnostic, and the hardest failure here to diagnose:
# Avoid this
CMD ["sh", "-c", "run-migrations && start-server"]
If the migration cannot reach the database — a paused instance, an expired connection string, a cold-start timeout — it never exits, the server never starts, and the port never opens. The deploy itself already succeeded, so the preview shows as Live while nothing answers. Run migrations as a separate, visible step instead.
Related pages
- getting-started.md — your first deploy end to end
- configuration.md —
.previewops.yamlreference - databases.md — connecting a database to a preview
- ui-deploy.md — deploying from the dashboard