Preview Environments for Go Gin: Automated Per-PR Deployments with Bunnyshell
Why Preview Environments for Go Gin?
Go Gin APIs are fast to build and fast to compile — but they're still as vulnerable as any other stack to the classic "works on my machine" problem. A PR that refactors a database query, adds a new endpoint, or changes GORM migrations looks fine in CI, but when it hits the shared staging server you discover it conflicts with migrations from another branch, or the DATABASE_URL points to the wrong host.
Preview environments solve this. Every pull request gets its own isolated deployment — Go Gin app, PostgreSQL database — running in Kubernetes with production-like configuration. Reviewers click a link and test the actual running API, not just the diff.
With Bunnyshell, you get:
- Automatic deployment — A new environment spins up for every PR, no manual steps
- Production parity — Same static binary, same database engine, same infrastructure as prod
- Isolation — Each PR environment is fully independent; no shared staging conflicts
- Automatic cleanup — Environments are destroyed when the PR is merged or closed
Go Gin is an especially good fit for preview environments because of its multi-stage Docker build: the final runtime image contains only the compiled static binary plus a minimal Alpine base, coming in at roughly 15MB. This means preview environments start in under a minute and use minimal cluster resources.
Choose Your Approach
Bunnyshell supports three ways to set up preview environments for Go Gin. Pick the one that fits your workflow:
| Approach | Best for | Complexity | CI/CD maintenance |
|---|---|---|---|
| Approach A: Bunnyshell UI | Teams that want the fastest setup with zero pipeline maintenance | Easiest | None — Bunnyshell manages webhooks automatically |
| Approach B: Docker Compose Import | Teams already using docker-compose.yml for local development | Easy | None — import converts to Bunnyshell config automatically |
| Approach C: Helm Charts | Teams with existing Helm infrastructure or complex K8s needs | Advanced | Optional — can use CLI or Bunnyshell UI |
All three approaches end the same way: a toggle in Bunnyshell Settings that enables automatic preview environments for every PR. No GitHub Actions, no GitLab CI pipelines to maintain — Bunnyshell adds webhooks to your Git provider and listens for PR events.
Prerequisites: Prepare Your Go Gin App
Regardless of which approach you choose, your Go Gin app needs two things: a multi-stage Dockerfile and the right configuration for running behind a Kubernetes ingress.
1. Create a Multi-Stage Dockerfile
The multi-stage approach keeps your final image tiny (~15MB) by using a full Go build environment only for compilation, then copying just the compiled binary into a minimal Alpine runtime:
1# ── Stage 1: Build ──
2FROM golang:1.22-alpine AS builder
3
4# Install build dependencies (for CGO with lib/pq if needed)
5RUN apk add --no-cache gcc musl-dev
6
7WORKDIR /app
8
9# Download dependencies first (better layer caching)
10COPY go.mod go.sum ./
11RUN go mod download
12
13# Copy source and build
14COPY . .
15RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
16 go build -ldflags="-w -s" -o server ./cmd/server
17
18# ── Stage 2: Runtime ──
19FROM alpine:3.19
20
21# Add CA certificates for HTTPS outbound calls
22RUN apk add --no-cache ca-certificates tzdata
23
24WORKDIR /app
25
26# Copy only the compiled binary
27COPY /app/server .
28
29# Run as non-root user
30RUN addgroup -S appgroup && adduser -S appuser -G appgroup
31USER appuser
32
33EXPOSE 8080
34CMD ["./server"]This produces an image of approximately 15MB — dramatically smaller than a golang:1.22 image (~800MB) or even python:3.12-slim (~150MB). Each preview environment uses less disk on your cluster and pulls faster.
Important: The app must listen on
0.0.0.0, not127.0.0.1. Use thePORTenvironment variable (defaulting to8080) so Bunnyshell can inject the correct port if needed.
If you use lib/pq with CGO enabled (CGO_ENABLED=1), you need gcc and musl-dev in the builder stage and must link against musl statically. For a fully static binary (no Alpine needed), use pgx/v5 with CGO_ENABLED=0 instead — it's a pure Go PostgreSQL driver.
2. Configure Go Gin for Kubernetes
Gin running behind a Kubernetes ingress needs two things: release mode and proper handling of X-Forwarded-Proto headers.
1// main.go
2package main
3
4import (
5 "log"
6 "os"
7
8 "github.com/gin-gonic/gin"
9 "gorm.io/driver/postgres"
10 "gorm.io/gorm"
11)
12
13func main() {
14 // Use release mode in production — disables debug logging
15 gin.SetMode(gin.ReleaseMode)
16
17 port := os.Getenv("PORT")
18 if port == "" {
19 port = "8080"
20 }
21
22 // Database connection via DATABASE_URL or individual vars
23 dsn := os.Getenv("DATABASE_URL")
24 if dsn == "" {
25 dsn = buildDSN()
26 }
27
28 db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
29 if err != nil {
30 log.Fatalf("failed to connect to database: %v", err)
31 }
32
33 // Run auto-migrations (or use golang-migrate for versioned migrations)
34 if err := db.AutoMigrate(&User{}, &Post{}); err != nil {
35 log.Fatalf("failed to run migrations: %v", err)
36 }
37
38 r := gin.New()
39 r.Use(gin.Logger(), gin.Recovery())
40
41 // Trust the Kubernetes ingress as a proxy
42 // This makes c.Request.TLS populated correctly and ensures
43 // redirect URLs use https:// rather than http://
44 if err := r.SetTrustedProxies([]string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}); err != nil {
45 log.Fatalf("failed to set trusted proxies: %v", err)
46 }
47
48 // Health check endpoint (Kubernetes liveness/readiness probe)
49 r.GET("/healthz", func(c *gin.Context) {
50 c.JSON(200, gin.H{"status": "ok"})
51 })
52
53 // Your routes here
54 api := r.Group("/api/v1")
55 {
56 api.GET("/users", listUsers(db))
57 api.POST("/users", createUser(db))
58 }
59
60 log.Printf("Starting server on :%s", port)
61 if err := r.Run("0.0.0.0:" + port); err != nil {
62 log.Fatalf("failed to start server: %v", err)
63 }
64}
65
66func buildDSN() string {
67 host := os.Getenv("DB_HOST")
68 if host == "" {
69 host = "localhost"
70 }
71 port := os.Getenv("DB_PORT")
72 if port == "" {
73 port = "5432"
74 }
75 user := os.Getenv("DB_USER")
76 if user == "" {
77 user = "gin"
78 }
79 password := os.Getenv("DB_PASSWORD")
80 name := os.Getenv("DB_NAME")
81 if name == "" {
82 name = "gin_db"
83 }
84 return "host=" + host + " port=" + port + " user=" + user +
85 " password=" + password + " dbname=" + name + " sslmode=disable"
86}gin.SetMode(gin.ReleaseMode) must be called before gin.New() or gin.Default(). In release mode, Gin omits debug route printing and uses more efficient logging. If you forget this, your preview environment logs will be flooded with debug output.
Go Gin Deployment Checklist
- Multi-stage Dockerfile with
golang:1.22-alpinebuilder +alpine:3.19runtime -
gin.SetMode(gin.ReleaseMode)called before router initialization -
r.SetTrustedProxies(...)configured for Kubernetes pod CIDR ranges - App listens on
0.0.0.0:8080(notlocalhostor127.0.0.1) -
PORTenv var respected with fallback to8080 -
DATABASE_URLor individualDB_*vars configured -
GIN_MODE=releaseset in Kubernetes environment -
/healthzendpoint for Kubernetes liveness/readiness probes -
CGO_ENABLED=0for a fully static binary (recommended)
Approach A: Bunnyshell UI — Zero CI/CD Maintenance
This is the easiest approach. You connect your repo, paste a YAML config, deploy, and flip a toggle. No CI/CD pipelines to write or maintain — Bunnyshell automatically adds webhooks to your Git provider and creates/destroys preview environments when PRs are opened/closed.
Step 1: Create a Project and Environment
- Log into Bunnyshell
- Click Create project and name it (e.g., "Go Gin API")
- Inside the project, click Create environment and name it (e.g., "gin-main")
Step 2: Define the Environment Configuration
Click Configuration in your environment view and paste this bunnyshell.yaml:
1kind: Environment
2name: gin-preview
3type: primary
4
5environmentVariables:
6 DB_PASSWORD: SECRET["your-db-password"]
7
8components:
9 # ── Go Gin Application ──
10 - kind: Application
11 name: gin-app
12 gitRepo: 'https://github.com/your-org/your-gin-repo.git'
13 gitBranch: main
14 gitApplicationPath: /
15 dockerCompose:
16 build:
17 context: .
18 dockerfile: Dockerfile
19 environment:
20 GIN_MODE: release
21 PORT: '8080'
22 DATABASE_URL: 'postgres://gin:{{ env.vars.DB_PASSWORD }}@postgres:5432/gin_db?sslmode=disable'
23 ports:
24 - '8080:8080'
25 hosts:
26 - hostname: 'api-{{ env.base_domain }}'
27 path: /
28 servicePort: 8080
29 dependsOn:
30 - postgres
31
32 # ── PostgreSQL Database ──
33 - kind: Database
34 name: postgres
35 dockerCompose:
36 image: 'postgres:16-alpine'
37 environment:
38 POSTGRES_DB: gin_db
39 POSTGRES_USER: gin
40 POSTGRES_PASSWORD: '{{ env.vars.DB_PASSWORD }}'
41 ports:
42 - '5432:5432'
43
44volumes:
45 - name: postgres-data
46 mount:
47 component: postgres
48 containerPath: /var/lib/postgresql/data
49 size: 1GiReplace your-org/your-gin-repo with your actual repository. Save the configuration.
Because GORM AutoMigrate runs at startup, your schema is always up to date when the preview environment starts. If you use golang-migrate instead, add a migration init container or run bns exec COMPONENT_ID -- ./migrate -path ./migrations -database "$DATABASE_URL" up after deploy.
Step 3: Deploy
Click the Deploy button, select your Kubernetes cluster, and click Deploy Environment. Bunnyshell will:
- Build your Go Gin Docker image using the multi-stage Dockerfile (~15MB final image)
- Pull the PostgreSQL image
- Deploy everything into an isolated Kubernetes namespace
- Generate HTTPS URLs automatically with DNS
The build stage compiles your Go binary inside the cluster. The final image is pulled from the registry and started in seconds. Monitor the deployment in the environment detail page. When status shows Running, click Endpoints to access your live API.
Step 4: Verify the Deployment
1export BUNNYSHELL_TOKEN=your-api-token
2
3# Get component IDs
4bns components list --environment ENV_ID --output json | jq '._embedded.item[] | {id, name}'
5
6# Check health endpoint
7curl https://api-$(bns environments get ENV_ID --output json | jq -r '.baseDomain')/healthz
8
9# Run a database connectivity check
10bns exec COMPONENT_ID -- ./server --check-dbStep 5: Enable Automatic Preview Environments
This is the magic step — no CI/CD configuration needed:
- In your environment, go to Settings
- Find the Ephemeral environments section
- Toggle "Create ephemeral environments on pull request" to ON
- Toggle "Destroy environment after merge or close pull request" to ON
- Select the Kubernetes cluster for ephemeral environments
That's it. Bunnyshell automatically adds a webhook to your Git provider (GitHub, GitLab, or Bitbucket). From now on:
- Open a PR → Bunnyshell creates an ephemeral environment with the PR's branch
- Push to PR → The environment redeploys with the latest changes (Go recompiles in the build stage)
- Bunnyshell posts a comment on the PR with a link to the live API
- Merge or close the PR → The ephemeral environment is automatically destroyed
Note: The primary environment must be in Running or Stopped status before ephemeral environments can be created from it.
Approach B: Docker Compose Import
Already have a docker-compose.yml for local development? Bunnyshell can import it directly and convert it to its environment format. No manual YAML writing required.
Step 1: Add a docker-compose.yml to Your Repo
If you don't already have one, create docker-compose.yml in your repo root:
1version: '3.8'
2
3services:
4 gin-app:
5 build:
6 context: .
7 dockerfile: Dockerfile
8 ports:
9 - '8080:8080'
10 environment:
11 GIN_MODE: debug
12 PORT: '8080'
13 DATABASE_URL: 'postgres://gin:gin@postgres:5432/gin_db?sslmode=disable'
14 depends_on:
15 postgres:
16 condition: service_healthy
17
18 postgres:
19 image: postgres:16-alpine
20 environment:
21 POSTGRES_DB: gin_db
22 POSTGRES_USER: gin
23 POSTGRES_PASSWORD: gin
24 volumes:
25 - postgres-data:/var/lib/postgresql/data
26 healthcheck:
27 test: ["CMD-SHELL", "pg_isready -U gin -d gin_db"]
28 interval: 5s
29 timeout: 5s
30 retries: 5
31
32volumes:
33 postgres-data:Step 2: Import into Bunnyshell
- Create a Project and Environment in Bunnyshell (same as Approach A, Step 1)
- Click Define environment
- Select your Git account and repository
- Set the branch (e.g.,
main) and the path todocker-compose.yml(use/if it's in the root) - Click Continue — Bunnyshell parses and validates your Docker Compose file
Bunnyshell automatically detects:
- All services (gin-app, postgres)
- Exposed ports
- Build configurations (Dockerfile, multi-stage is fully supported)
- Volumes
- Environment variables
It converts everything into a bunnyshell.yaml environment definition.
Important: The
docker-compose.ymlis only read during the initial import. Subsequent changes to the file won't auto-propagate — edit the environment configuration in Bunnyshell instead.
Step 3: Adjust the Configuration
After import, go to Configuration in the environment view and update:
- Replace hardcoded secrets with
SECRET["..."]syntax - Switch
GIN_MODEfromdebugtorelease - Update
DATABASE_URLusing Bunnyshell interpolation:
DATABASE_URL: 'postgres://gin:{{ env.vars.DB_PASSWORD }}@postgres:5432/gin_db?sslmode=disable'
GIN_MODE: release- Add the
hostsblock so Bunnyshell generates the ingress URL:
1hosts:
2 - hostname: 'api-{{ env.base_domain }}'
3 path: /
4 servicePort: 8080Step 4: Deploy and Enable Preview Environments
Same as Approach A — click Deploy, then go to Settings and toggle on ephemeral environments.
Best Practices for Docker Compose with Bunnyshell
- Kubernetes doesn't guarantee
depends_onordering — Thehealthcheckcondition in Docker Compose isn't respected in Kubernetes. Make your Go app retry database connections on startup. GORM's connection pooling handles transient failures gracefully, but you can add an explicit retry:
1// Retry database connection up to 10 times with exponential backoff
2var db *gorm.DB
3var err error
4for i := 0; i < 10; i++ {
5 db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
6 if err == nil {
7 break
8 }
9 log.Printf("DB not ready (attempt %d/10): %v", i+1, err)
10 time.Sleep(time.Duration(i+1) * 2 * time.Second)
11}
12if err != nil {
13 log.Fatalf("could not connect to database after 10 attempts: %v", err)
14}- Use Bunnyshell interpolation for dynamic API base URLs in frontend configs:
# Bunnyshell environment config (after import)
API_BASE_URL: 'https://{{ components.gin-app.ingress.hosts[0] }}'Approach C: Helm Charts
For teams with existing Helm infrastructure or complex Kubernetes requirements (custom ingress, HPA, service mesh). Helm gives you full control over every Kubernetes resource.
Step 1: Create a Helm Chart
Structure your Go Gin Helm chart in your repo:
1helm/gin/
2├── Chart.yaml
3├── values.yaml
4└── templates/
5 ├── deployment.yaml
6 ├── service.yaml
7 ├── ingress.yaml
8 └── configmap.yamlA minimal values.yaml:
1replicaCount: 1
2image:
3 repository: ""
4 tag: latest
5service:
6 port: 8080
7ingress:
8 enabled: true
9 className: bns-nginx
10 host: ""
11env:
12 GIN_MODE: release
13 PORT: "8080"
14 DATABASE_URL: ""
15resources:
16 requests:
17 memory: "32Mi"
18 cpu: "50m"
19 limits:
20 memory: "128Mi"
21 cpu: "500m"
22livenessProbe:
23 httpGet:
24 path: /healthz
25 port: 8080
26 initialDelaySeconds: 5
27readinessProbe:
28 httpGet:
29 path: /healthz
30 port: 8080
31 initialDelaySeconds: 3Because the final Go Gin image is ~15MB and the binary starts in milliseconds, you can set very aggressive liveness and readiness probe intervals. This means Kubernetes detects healthy pods faster, reducing the time to green for each preview environment deploy.
Step 2: Define the Bunnyshell Configuration
Create a bunnyshell.yaml using Helm components:
1kind: Environment
2name: gin-helm
3type: primary
4
5environmentVariables:
6 DB_PASSWORD: SECRET["your-db-password"]
7 POSTGRES_DB: gin_db
8 POSTGRES_USER: gin
9
10components:
11 # ── Docker Image Build ──
12 - kind: DockerImage
13 name: gin-image
14 context: /
15 dockerfile: Dockerfile
16 gitRepo: 'https://github.com/your-org/your-gin-repo.git'
17 gitBranch: main
18 gitApplicationPath: /
19
20 # ── PostgreSQL via Helm ──
21 - kind: Helm
22 name: postgres
23 runnerImage: 'dtzar/helm-kubectl:3.8.2'
24 deploy:
25 - |
26 cat << EOF > pg_values.yaml
27 global:
28 storageClass: bns-network-sc
29 auth:
30 postgresPassword: {{ env.vars.DB_PASSWORD }}
31 database: {{ env.vars.POSTGRES_DB }}
32 EOF
33 - 'helm repo add bitnami https://charts.bitnami.com/bitnami'
34 - 'helm upgrade --install --namespace {{ env.k8s.namespace }}
35 --post-renderer /bns/helpers/helm/bns_post_renderer
36 -f pg_values.yaml postgres bitnami/postgresql --version 11.9.11'
37 - |
38 POSTGRES_HOST="postgres-postgresql.{{ env.k8s.namespace }}.svc.cluster.local"
39 destroy:
40 - 'helm uninstall postgres --namespace {{ env.k8s.namespace }}'
41 start:
42 - 'kubectl scale --replicas=1 --namespace {{ env.k8s.namespace }}
43 statefulset/postgres-postgresql'
44 stop:
45 - 'kubectl scale --replicas=0 --namespace {{ env.k8s.namespace }}
46 statefulset/postgres-postgresql'
47 exportVariables:
48 - POSTGRES_HOST
49
50 # ── Go Gin App via Helm ──
51 - kind: Helm
52 name: gin-app
53 runnerImage: 'dtzar/helm-kubectl:3.8.2'
54 deploy:
55 - |
56 cat << EOF > gin_values.yaml
57 replicaCount: 1
58 image:
59 repository: {{ components.gin-image.image }}
60 service:
61 port: 8080
62 ingress:
63 enabled: true
64 className: bns-nginx
65 host: api-{{ env.base_domain }}
66 env:
67 GIN_MODE: release
68 PORT: "8080"
69 DATABASE_URL: 'postgres://{{ env.vars.POSTGRES_USER }}:{{ env.vars.DB_PASSWORD }}@{{ components.postgres.exported.POSTGRES_HOST }}:5432/{{ env.vars.POSTGRES_DB }}?sslmode=disable'
70 EOF
71 - 'helm upgrade --install --namespace {{ env.k8s.namespace }}
72 --post-renderer /bns/helpers/helm/bns_post_renderer
73 -f gin_values.yaml gin-{{ env.unique }} ./helm/gin'
74 destroy:
75 - 'helm uninstall gin-{{ env.unique }} --namespace {{ env.k8s.namespace }}'
76 start:
77 - 'helm upgrade --namespace {{ env.k8s.namespace }}
78 --post-renderer /bns/helpers/helm/bns_post_renderer
79 --reuse-values --set replicaCount=1 gin-{{ env.unique }} ./helm/gin'
80 stop:
81 - 'helm upgrade --namespace {{ env.k8s.namespace }}
82 --post-renderer /bns/helpers/helm/bns_post_renderer
83 --reuse-values --set replicaCount=0 gin-{{ env.unique }} ./helm/gin'
84 gitRepo: 'https://github.com/your-org/your-gin-repo.git'
85 gitBranch: main
86 gitApplicationPath: /helm/ginKey: Always include
--post-renderer /bns/helpers/helm/bns_post_rendererin your helm commands. This adds labels so Bunnyshell can track resources, show logs, and manage component lifecycle.
Step 3: Deploy and Enable Preview Environments
Same flow: paste the config in Configuration, hit Deploy, then enable ephemeral environments in Settings.
Enabling Preview Environments (All Approaches)
Regardless of which approach you used, enabling automatic preview environments is the same:
- Ensure your primary environment has been deployed at least once (Running or Stopped status)
- Go to Settings in your environment
- Toggle "Create ephemeral environments on pull request" → ON
- Toggle "Destroy environment after merge or close pull request" → ON
- Select the target Kubernetes cluster
What happens next:
- Bunnyshell adds a webhook to your Git provider automatically
- When a developer opens a PR, Bunnyshell creates an ephemeral environment cloned from the primary, using the PR's branch
- Bunnyshell posts a comment on the PR with a direct link to the running API
- When the PR is merged or closed, the ephemeral environment is automatically destroyed
No GitHub Actions. No GitLab CI pipelines. No maintenance. It just works.
The Go multi-stage build runs inside the cluster during each deploy. Because Go compilation is deterministic and Docker layer caching is used, subsequent builds (e.g., when you push a new commit to a PR) are fast — only changed files are recompiled.
Optional: CI/CD Integration via CLI
If you prefer to control preview environments from your CI/CD pipeline (e.g., to run post-deploy smoke tests or apply golang-migrate migrations), use the Bunnyshell CLI:
1# Install
2brew install bunnyshell/tap/bunnyshell-cli
3
4# Authenticate
5export BUNNYSHELL_TOKEN=your-api-token
6
7# Create, deploy, and run migrations in one flow
8bns environments create --from-path bunnyshell.yaml --name "pr-123" --project PROJECT_ID --k8s CLUSTER_ID
9bns environments deploy --id ENV_ID --wait
10
11# Run golang-migrate migrations (if not using GORM AutoMigrate)
12bns exec COMPONENT_ID -- ./migrate -path ./migrations -database "$DATABASE_URL" up
13
14# Run API smoke tests against the preview environment
15bns exec COMPONENT_ID -- go test ./tests/integration/... -tags=integrationRemote Development and Debugging
Bunnyshell makes it easy to develop and debug directly against any environment — primary or ephemeral.
Port Forwarding
Connect your local tools to the remote database:
1# Forward PostgreSQL to local port 15432
2bns port-forward 15432:5432 --component POSTGRES_COMPONENT_ID
3
4# Connect with psql
5psql -h localhost -p 15432 -U gin gin_db
6
7# Forward the API to local port 18080 for direct testing
8bns port-forward 18080:8080 --component GIN_COMPONENT_ID
9
10# Hit the API locally
11curl http://localhost:18080/healthz
12curl http://localhost:18080/api/v1/usersExecute Commands in the Container
1# Run a health check
2bns exec COMPONENT_ID -- ./server --check-db
3
4# Apply golang-migrate migrations manually
5bns exec COMPONENT_ID -- ./migrate -path ./migrations -database "$DATABASE_URL" up
6
7# Show current migration version
8bns exec COMPONENT_ID -- ./migrate -path ./migrations -database "$DATABASE_URL" version
9
10# Open a shell (Alpine has sh, not bash)
11bns exec COMPONENT_ID -- sh
12
13# Check binary size (highlights the ~15MB image benefit)
14bns exec COMPONENT_ID -- ls -lh ./serverLive Logs
1# Stream logs in real time
2bns logs --component COMPONENT_ID -f
3
4# Last 200 lines
5bns logs --component COMPONENT_ID --tail 200
6
7# Logs from the last 5 minutes
8bns logs --component COMPONENT_ID --since 5mLive Code Sync
For active development, sync your local code changes to the remote container in real time:
1bns remote-development up --component COMPONENT_ID
2# Edit Go files locally — changes sync automatically
3# Note: Go requires recompilation; use Air for hot-reload in development mode
4bns remote-development downFor live code sync to work with Go hot-reload, include Air in your development Dockerfile and use a separate Dockerfile.dev for remote development. Air watches for file changes and recompiles automatically. Your production Dockerfile should remain the multi-stage build without Air.
Troubleshooting
| Issue | Solution |
|---|---|
| 502 Bad Gateway | Gin isn't listening on 0.0.0.0:8080. Check your server startup: r.Run("0.0.0.0:" + port) — never localhost. |
| Mixed content / HTTPS issues | Configure r.SetTrustedProxies(...) with your cluster CIDR. Without this, Gin doesn't trust X-Forwarded-Proto. |
| Redirect loops on HTTPS | Ensure SetTrustedProxies includes the ingress controller's IP range. Gin uses X-Forwarded-Proto only for trusted proxies. |
dial tcp: connection refused to postgres | PostgreSQL isn't ready yet. Add a retry loop in your main function before AutoMigrate. |
| GORM AutoMigrate fails | Check that DATABASE_URL uses postgres (the component name) as the host, not localhost. |
| golang-migrate: no change | Ensure migration files are embedded in the binary with //go:embed or copied into the image. |
Build fails: go: no module providing | Run go mod tidy locally and commit the updated go.sum. The build stage runs go mod download. |
| Image build slow | go.mod and go.sum are copied before source files — this enables Docker layer caching for dependencies. Ensure you haven't broken this order. |
| Container exits immediately | The binary panicked on startup. Add log.Fatalf error handling around DB connection and r.Run(). |
| 522 Connection timed out | Cluster may be behind a firewall. Verify Cloudflare IPs are whitelisted on the ingress controller. |
What's Next?
- Add Redis for caching — Add a Redis component and use
go-redis/redisfor caching hot query results in preview environments - Add a worker process — Add another
Applicationcomponent for background job processing (e.g., usinggocraft/workorriverqueue/river) - Integration test against preview URLs — In your CI, hit the Bunnyshell API to get the preview URL and run
go test ./tests/integration/...against it - Monitor with Sentry — Pass
SENTRY_DSNas an environment variable and usegetsentry/sentry-gofor error tracking in preview environments - Optimize build caching — Use Bunnyshell's registry to push built images and skip the build step on unchanged branches
Related Resources
- Bunnyshell Quickstart Guide
- Docker Compose with Bunnyshell
- Helm with Bunnyshell
- Bunnyshell CLI Reference
- Ephemeral Environments — Learn more about the concept
- Preview Environments for Django — Django-specific guide
- All Guides — More technical guides
Ship faster starting today.
14-day full-feature trial. No credit card required. Pay-as-you-go from $0.007/min per environment.