Deploying Gin (Go) on Hop3¶
This guide walks you through deploying a Gin web application on Hop3. By the end, you'll have a production-ready, high-performance Go API running on your own infrastructure.
Prerequisites¶
Before you begin, ensure you have:
- A Hop3 server - Follow the Installation Guide if you haven't set one up yet
- The Hop3 CLI - Installed on your local machine
- Go 1.21+ - Install from go.dev
- Git - For version control and deployment
Verify your local setup:
Step 1: Create a New Gin Application¶
Create the project directory and initialize Go module:
Install Gin:
Step 2: Create the Application¶
Create the main application file:
package main
import (
"net/http"
"os"
"runtime"
"time"
"github.com/gin-gonic/gin"
)
var startTime = time.Now()
func main() {
// Set Gin to release mode in production
if os.Getenv("GIN_MODE") == "release" {
gin.SetMode(gin.ReleaseMode)
}
r := gin.Default()
// Welcome page
r.GET("/", func(c *gin.Context) {
c.Header("Content-Type", "text/html")
c.String(http.StatusOK, `
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Hop3</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #00ADD8 0%, #5DC9E2 100%);
color: white;
}
.container { text-align: center; padding: 2rem; }
h1 { font-size: 3rem; margin-bottom: 1rem; }
p { font-size: 1.25rem; opacity: 0.9; }
a {
display: inline-block;
margin-top: 1rem;
padding: 0.75rem 1.5rem;
background: rgba(255,255,255,0.2);
border-radius: 8px;
color: white;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<h1>Hello from Hop3!</h1>
<p>Your Gin application is running.</p>
<p>Current time: `+time.Now().Format(time.RFC3339)+`</p>
<a href="/api/info">API Info</a>
</div>
</body>
</html>`)
})
// Health check endpoint
r.GET("/up", func(c *gin.Context) {
c.String(http.StatusOK, "OK")
})
r.GET("/health", func(c *gin.Context) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"timestamp": time.Now().Format(time.RFC3339),
"uptime": time.Since(startTime).String(),
"memory": gin.H{
"alloc": m.Alloc / 1024 / 1024,
"totalAlloc": m.TotalAlloc / 1024 / 1024,
"sys": m.Sys / 1024 / 1024,
},
})
})
// API info endpoint
r.GET("/api/info", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"name": "hop3-tuto-gin",
"version": "1.0.0",
"go_version": runtime.Version(),
"os": runtime.GOOS,
"arch": runtime.GOARCH,
})
})
// Example CRUD endpoints
r.GET("/api/items", getItems)
r.GET("/api/items/:id", getItem)
r.POST("/api/items", createItem)
r.PUT("/api/items/:id", updateItem)
r.DELETE("/api/items/:id", deleteItem)
// Get port from environment
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
r.Run(":" + port)
}
// Item represents a simple data model
type Item struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Price float64 `json:"price"`
}
// In-memory storage
var items = map[string]Item{
"1": {ID: "1", Name: "Item 1", Description: "First item", Price: 9.99},
"2": {ID: "2", Name: "Item 2", Description: "Second item", Price: 19.99},
}
func getItems(c *gin.Context) {
itemList := make([]Item, 0, len(items))
for _, item := range items {
itemList = append(itemList, item)
}
c.JSON(http.StatusOK, itemList)
}
func getItem(c *gin.Context) {
id := c.Param("id")
if item, ok := items[id]; ok {
c.JSON(http.StatusOK, item)
return
}
c.JSON(http.StatusNotFound, gin.H{"error": "Item not found"})
}
func createItem(c *gin.Context) {
var item Item
if err := c.ShouldBindJSON(&item); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
items[item.ID] = item
c.JSON(http.StatusCreated, item)
}
func updateItem(c *gin.Context) {
id := c.Param("id")
if _, ok := items[id]; !ok {
c.JSON(http.StatusNotFound, gin.H{"error": "Item not found"})
return
}
var item Item
if err := c.ShouldBindJSON(&item); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
item.ID = id
items[id] = item
c.JSON(http.StatusOK, item)
}
func deleteItem(c *gin.Context) {
id := c.Param("id")
if _, ok := items[id]; !ok {
c.JSON(http.StatusNotFound, gin.H{"error": "Item not found"})
return
}
delete(items, id)
c.Status(http.StatusNoContent)
}
Step 3: Tidy Dependencies¶
Step 4: Build and Test¶
Build the application:
Test the application:
./hop3-tuto-gin &
APP_PID=$!
sleep 2
curl -s http://localhost:8080/health | head -1 || echo "Test completed"
kill $APP_PID 2>/dev/null || true
Step 5: Create Deployment Configuration¶
Create a Procfile¶
# Pre-build: Build the binary
prebuild: go build -o hop3-tuto-gin .
# Main web process
web: ./hop3-tuto-gin
Create hop3.toml¶
[metadata]
id = "hop3-tuto-gin"
version = "1.0.0"
title = "My Gin Application"
[build]
before-build = ["go build -ldflags='-s -w' -o hop3-tuto-gin ."]
packages = ["golang"]
[run]
start = "./hop3-tuto-gin"
[env]
GIN_MODE = "release"
[port]
web = 8080
[healthcheck]
path = "/up"
timeout = 30
interval = 60
Verify the deployment files:
Step 6: Initialize Git Repository¶
# Binary
hop3-tuto-gin
# Dependencies
vendor/
# IDE
.idea/
.vscode/
*.swp
# OS
.DS_Store
# Environment
.env
Step 7: Deploy to Hop3¶
Initialize (First Time Only)¶
Set Environment Variables¶
Deploy¶
Deploy the application (first deployment creates the app):
Set Hostname¶
Configure the hostname for nginx proxy:
Apply Configuration¶
Redeploy to apply the hostname configuration:
Wait for the application to start:
Verify Deployment¶
Managing Your Application¶
# Restart the application
hop3 app:restart hop3-tuto-gin
# View logs
hop3 app:logs hop3-tuto-gin
# View/set environment variables
hop3 config:show hop3-tuto-gin
hop3 config:set hop3-tuto-gin NEW_VAR=value
# Scale workers
hop3 ps:scale hop3-tuto-gin web=2
Advanced Configuration¶
Adding PostgreSQL with GORM¶
package main
import (
"os"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var db *gorm.DB
func initDB() {
dsn := os.Getenv("DATABASE_URL")
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Auto-migrate models
db.AutoMigrate(&Item{})
}
Adding Redis¶
import "github.com/redis/go-redis/v9"
var rdb *redis.Client
func initRedis() {
opt, _ := redis.ParseURL(os.Getenv("REDIS_URL"))
rdb = redis.NewClient(opt)
}
JWT Authentication¶
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
return
}
c.Next()
}
}
Structured Logging¶
import "go.uber.org/zap"
var logger *zap.Logger
func initLogger() {
var err error
if os.Getenv("GIN_MODE") == "release" {
logger, err = zap.NewProduction()
} else {
logger, err = zap.NewDevelopment()
}
if err != nil {
panic(err)
}
}
Graceful Shutdown¶
func main() {
// ... setup routes ...
srv := &http.Server{
Addr: ":" + port,
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
}
Troubleshooting¶
Build Failures¶
- Check Go version:
go version - Verify dependencies:
go mod tidy - Check for syntax errors:
go build -v
Runtime Errors¶
- Verify PORT environment variable
- Check GIN_MODE for production
Memory Usage¶
Go applications are typically memory-efficient, but monitor with /health endpoint.
Example Files¶
Complete hop3.toml¶
[metadata]
id = "hop3-tuto-gin"
version = "1.0.0"
title = "My Gin Application"
[build]
before-build = ["go build -ldflags='-s -w' -o hop3-tuto-gin ."]
packages = ["golang"]
[run]
start = "./hop3-tuto-gin"
[env]
GIN_MODE = "release"
[port]
web = 8080
[healthcheck]
path = "/up"
timeout = 30
interval = 60
[[provider]]
name = "postgres"
plan = "standard"
[[provider]]
name = "redis"
plan = "basic"