Deploying a net/http App
Go’s net/http package is the standard library solution for building high-performance web servers and RESTful APIs. As part of Go’s built-in standard library, net/http provides everything needed to create robust HTTP servers without external dependencies. It’s lightweight, efficient, and powers some of the largest web applications in the world.
Deploying net/http applications on Klutch.sh provides a managed platform for running your Go servers with automatic scaling, persistent storage support, and integrated monitoring capabilities. This comprehensive guide covers everything you need to deploy, configure, and manage net/http applications in production.
What is net/http?
net/http is Go’s standard library HTTP package offering:
- Built-in Standard Library: No external dependencies required
- High Performance: Efficient request handling with minimal overhead
- Full HTTP/1.1 Support: Complete HTTP protocol implementation
- Server and Client: Both server and client functionality in one package
- Middleware Support: Pluggable request handlers and middleware
- TLS/HTTPS: Built-in SSL/TLS support for secure connections
- Graceful Shutdown: Proper connection handling and cleanup
- Zero Configuration: Works out of the box with sensible defaults
- Production Ready: Used in production by thousands of applications
Prerequisites
Before deploying a net/http application to Klutch.sh, ensure you have:
- Go 1.20 or higher installed on your local machine
- Git for version control and pushing code to GitHub
- GitHub account for repository management
- Klutch.sh account with project creation permissions
- Basic Go knowledge for understanding net/http development
- Docker (optional, only if using Dockerfile deployment method)
Getting Started: Create Your First net/http Application
Step 1: Initialize Your Project
Create a new directory for your net/http application and initialize a Go module:
mkdir my-net-http-appcd my-net-http-appgo mod init my-net-http-appStep 2: Create a Basic net/http Server
Create a main.go file with a simple HTTP server:
package main
import ( "fmt" "log" "net/http" "os")
func main() { // Register request handlers http.HandleFunc("/", homeHandler) http.HandleFunc("/api/hello", helloHandler) http.HandleFunc("/health", healthHandler)
// Get port from environment variable or default to 8080 port := os.Getenv("PORT") if port == "" { port = "8080" }
// Log startup message log.Printf("Server starting on port %s", port)
// Start the server if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatalf("Server error: %v", err) }}
func homeHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Welcome to net/http on Klutch.sh!")}
func helloHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{"message": "Hello from net/http!"}`)}
func healthHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{"status": "healthy"}`)}Step 3: Test Your Application Locally
Run your net/http application locally:
go run main.goVisit http://localhost:8080 in your browser or use curl to test:
curl http://localhost:8080/curl http://localhost:8080/api/hellocurl http://localhost:8080/healthStep 4: Prepare for Deployment
Ensure your application reads the PORT environment variable (as shown in Step 2) and includes error handling for graceful operation.
Deploying Without a Dockerfile (Using Nixpacks)
Klutch.sh uses Nixpacks to automatically detect, build, and deploy Go applications. This method requires no Dockerfile configuration—simply push your code to GitHub and let Klutch.sh handle the rest.
Prepare Your Repository
-
Ensure your project has the following files in the root directory:
go.modandgo.sum(generated bygo mod initandgo mod tidy)main.go(your application entry point)- Any additional source files and packages
-
Push your net/http application to a GitHub repository:
git initgit add .git commit -m "Initial commit"git branch -M maingit remote add origin https://github.com/your-username/my-net-http-app.gitgit push -u origin main-
Log in to your Klutch.sh dashboard.
-
Create a new project by selecting your desired organization and naming your project (e.g., “My net/http API”).
-
Create a new app within your project with the following configuration:
- Repository: Select your net/http GitHub repository
- Branch: Select the branch to deploy (typically
mainormaster) - Traffic Type: Select HTTP (net/http serves HTTP traffic)
- Internal Port: Enter 8080 (the default port your net/http app listens on)
- Region: Choose the region closest to your users
- Compute: Select appropriate compute resources based on your application’s needs
- Instances: Set the number of instances for horizontal scaling
- Environment Variables: Add any configuration variables your app needs
-
Review your configuration and click “Create” to deploy. Klutch.sh will automatically:
- Detect your Go application
- Install dependencies using
go mod download - Build your binary with
go build -o net-http-app main.go - Deploy your application
-
Monitor the deployment progress in the Klutch.sh dashboard. Once deployment is complete, your application will be available at a URL like example-app.klutch.sh.
Customizing Build and Start Commands (Nixpacks)
If you need to customize the build process or startup behavior, Klutch.sh supports Nixpacks environment variables for command customization.
For example, if your application requires a custom build step or has a different entry point, set these environment variables in the Klutch.sh dashboard:
BUILD_COMMAND=go build -ldflags="-s -w" -o bin/app main.goSTART_COMMAND=./bin/appThese variables tell Nixpacks how to build and start your application. The example above creates an optimized binary in a bin directory.
Deploying With a Dockerfile
If you prefer complete control over your deployment environment, you can use a Dockerfile. Klutch.sh automatically detects and uses a Dockerfile if it exists in your repository’s root directory.
Create a Dockerfile
- Create a
Dockerfilein your project root with a multi-stage build for optimal image size:
# Stage 1: Build the applicationFROM golang:1.20-alpine AS builder
WORKDIR /app
# Copy go.mod and go.sumCOPY go.mod go.sum ./
# Download dependenciesRUN go mod download
# Copy application source codeCOPY . .
# Build the binary with optimizationsRUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o net-http-app main.go
# Stage 2: Create minimal runtime imageFROM alpine:latest
# Install ca-certificates for HTTPS requestsRUN apk --no-cache add ca-certificates
WORKDIR /app
# Copy the built binary from builder stageCOPY --from=builder /app/net-http-app .
# Expose port (must match your net/http app's port)EXPOSE 8080
# Set the default environment port variableENV PORT=8080
# Run the applicationCMD ["./net-http-app"]This Dockerfile uses a multi-stage build to minimize the final image size by including only the compiled binary and necessary runtime dependencies.
- Push your code to GitHub (including the Dockerfile):
git add Dockerfilegit commit -m "Add Dockerfile for production deployment"git push-
Log in to your Klutch.sh dashboard.
-
Create a new project (or use an existing one).
-
Create a new app with the following settings:
- Repository: Select your net/http GitHub repository
- Branch: Select your deployment branch
- Traffic Type: Select HTTP (net/http serves HTTP traffic)
- Internal Port: Enter 8080 (the port your net/http app listens on)
- Region, Compute, and Instances: Configure according to your needs
- Environment Variables: Add any required configuration variables
-
Click “Create” to deploy. Klutch.sh will automatically:
- Detect your Dockerfile
- Build your Docker image
- Deploy the containerized application to your Klutch.sh infrastructure
-
Once deployment is complete, your application will be accessible at a URL like example-app.klutch.sh.
Advanced Dockerfile Configuration
For more complex deployments, you can customize your Dockerfile further:
# Stage 1: BuildFROM golang:1.20-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./RUN go mod download
COPY . .
# Build with version information (optional)ARG VERSION=devRUN CGO_ENABLED=0 GOOS=linux go build \ -ldflags="-s -w -X main.Version=${VERSION}" \ -o net-http-app main.go
# Stage 2: RuntimeFROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/net-http-app .
EXPOSE 8080ENV PORT=8080
# Add health check (optional)HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
CMD ["./net-http-app"]Environment Variables and Configuration
net/http applications typically require environment variables for configuration. Manage these securely through the Klutch.sh dashboard.
Common Environment Variables
Set these in your Klutch.sh app configuration:
PORT=8080APP_ENV=productionLOG_LEVEL=infoDATABASE_URL=postgres://user:password@db.example.com/mydbAPI_KEY=your-api-key-hereSECRET_KEY=your-secret-key-hereCORS_ORIGINS=https://yourdomain.comReading Environment Variables in net/http
Update your main.go to read and use environment variables:
package main
import ( "fmt" "log" "net/http" "os")
func main() { // Read configuration from environment appEnv := os.Getenv("APP_ENV") logLevel := os.Getenv("LOG_LEVEL")
if appEnv == "production" { // Configure for production log.Println("Running in production mode") }
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello from net/http!") })
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{"status":"healthy"}`) })
port := os.Getenv("PORT") if port == "" { port = "8080" }
log.Printf("Starting server on port %s with log level %s", port, logLevel) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatalf("Server error: %v", err) }}Building a RESTful API with net/http
net/http is excellent for building RESTful APIs. Here’s a comprehensive example:
package main
import ( "encoding/json" "fmt" "log" "net/http" "os" "strconv" "strings")
type Article struct { ID int `json:"id"` Title string `json:"title"` Body string `json:"body"`}
var articles = []Article{ {ID: 1, Title: "Getting Started with Go", Body: "Go is a powerful programming language..."}, {ID: 2, Title: "net/http Tutorial", Body: "net/http provides all tools needed..."},}
func main() { // API routes http.HandleFunc("/api/articles", handleArticles) http.HandleFunc("/api/articles/", handleArticleDetail) http.HandleFunc("/health", handleHealth)
port := os.Getenv("PORT") if port == "" { port = "8080" }
log.Printf("API server running on port %s", port) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatalf("Server error: %v", err) }}
func handleArticles(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json")
if r.Method == http.MethodGet { // GET /api/articles - List all articles json.NewEncoder(w).Encode(articles) } else if r.Method == http.MethodPost { // POST /api/articles - Create new article var article Article if err := json.NewDecoder(r.Body).Decode(&article); err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprintln(w, `{"error":"Invalid request"}`) return } article.ID = len(articles) + 1 articles = append(articles, article) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(article) }}
func handleArticleDetail(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json")
// Extract ID from path parts := strings.Split(r.URL.Path, "/") if len(parts) < 4 { w.WriteHeader(http.StatusNotFound) fmt.Fprintln(w, `{"error":"Not found"}`) return }
id, err := strconv.Atoi(parts[3]) if err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprintln(w, `{"error":"Invalid ID"}`) return }
if r.Method == http.MethodGet { // GET /api/articles/:id - Get specific article for _, article := range articles { if article.ID == id { json.NewEncoder(w).Encode(article) return } } w.WriteHeader(http.StatusNotFound) fmt.Fprintln(w, `{"error":"Article not found"}`) } else if r.Method == http.MethodDelete { // DELETE /api/articles/:id - Delete article for i, article := range articles { if article.ID == id { articles = append(articles[:i], articles[i+1:]...) w.WriteHeader(http.StatusOK) fmt.Fprintln(w, `{"message":"Article deleted"}`) return } } w.WriteHeader(http.StatusNotFound) fmt.Fprintln(w, `{"error":"Article not found"}`) }}
func handleHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{"status":"healthy"}`)}Middleware and Request Handling
net/http supports middleware patterns for cross-cutting concerns:
package main
import ( "fmt" "log" "net/http" "os" "time")
// LoggingMiddleware logs all incoming requestsfunc LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.Proto)
next.ServeHTTP(w, r)
log.Printf("Completed in %v", time.Since(start)) })}
// AuthMiddleware validates API tokensfunc AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") if token == "" { w.WriteHeader(http.StatusUnauthorized) fmt.Fprintln(w, `{"error":"Missing authorization header"}`) return }
// Validate token (simplified example) if !isValidToken(token) { w.WriteHeader(http.StatusUnauthorized) fmt.Fprintln(w, `{"error":"Invalid token"}`) return }
next.ServeHTTP(w, r) })}
func isValidToken(token string) bool { // Implement your token validation logic return token != ""}
// Middleware chain helperfunc chain(handlers ...func(http.Handler) http.Handler) func(http.Handler) http.Handler { return func(final http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { last := final for i := len(handlers) - 1; i >= 0; i-- { last = handlers[i](last) } last.ServeHTTP(w, r) }) }}
func main() { // Public endpoints http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `{"status":"healthy"}`) })
// Protected endpoints with middleware protectedHandler := chain(LoggingMiddleware, AuthMiddleware)( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `{"data":"Protected data"}`) }), ) http.Handle("/api/protected", protectedHandler)
port := os.Getenv("PORT") if port == "" { port = "8080" }
log.Printf("Server running on port %s", port) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatalf("Server error: %v", err) }}Custom Domain Configuration
To access your net/http application on a custom domain instead of the default example-app.klutch.sh URL:
-
In the Klutch.sh dashboard, navigate to your application settings.
-
Find the Domains or Custom Domain section.
-
Add your custom domain (e.g.,
api.mycompany.com). -
Update your domain’s DNS records to point to Klutch.sh:
- Add a CNAME record pointing to your Klutch.sh app URL
- Or add A records pointing to the IP address provided by Klutch.sh
-
Verify the domain ownership through Klutch.sh’s verification process.
-
Once verified, your net/http application will be accessible at your custom domain.
Monitoring and Logging
Monitor your net/http deployment through Klutch.sh’s built-in tools:
-
In the Klutch.sh dashboard, view your application’s Logs to troubleshoot issues.
-
Check Metrics for CPU usage, memory consumption, and request throughput.
-
Set up Alerts to be notified of performance issues or errors.
-
Configure logging in your net/http application:
package main
import ( "fmt" "log" "net/http" "os" "time")
func main() { // Create a custom request handler with logging logHandler := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { startTime := time.Now() log.Printf("[%s] %s %s - User-Agent: %s", r.Method, r.URL.Path, r.Proto, r.Header.Get("User-Agent"))
next.ServeHTTP(w, r)
log.Printf("Response completed in %v", time.Since(startTime)) }) }
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{"status":"healthy"}`) })
port := os.Getenv("PORT") if port == "" { port = "8080" }
// Wrap all routes with logging http.Handle("/", logHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Welcome to net/http!") })))
log.Printf("Starting server on port %s", port) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatalf("Server error: %v", err) }}Graceful Shutdown
Properly handle shutdown to close connections cleanly:
package main
import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time")
func main() { server := &http.Server{ Addr: ":8080", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello from net/http!") }), }
// Start server in a goroutine go func() { log.Printf("Server starting on %s", server.Addr) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server error: %v", err) } }()
// Wait for interrupt signal sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) <-sigChan
log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel()
if err := server.Shutdown(ctx); err != nil { log.Fatalf("Server shutdown error: %v", err) }
log.Println("Server stopped")}Troubleshooting Common Issues
Application Fails to Build
Problem: Deployment fails during the build phase with dependency errors.
Solution:
- Ensure your
go.modandgo.sumfiles are up to date:go mod tidy - Verify all dependencies are listed in
go.mod - Check that your Go version matches the version in your Dockerfile or Nixpacks configuration
Port Binding Errors
Problem: Your application fails with a port binding error.
Solution:
- Ensure your net/http app reads the
PORTenvironment variable - Verify the internal port in Klutch.sh configuration matches what your app listens on (typically 8080)
- Check that no other services are binding to the same port
High Memory Usage
Problem: Application consuming excessive memory.
Solution:
- Review request handler efficiency
- Check for memory leaks in custom code
- Monitor goroutine count and resource usage
- Consider implementing connection pooling for external resources
Connection Timeouts
Problem: Client requests timing out.
Solution:
- Increase server read/write timeouts if needed
- Check for long-running operations in handlers
- Verify network connectivity and DNS resolution
- Review application logs for slow operations
Best Practices for Production net/http Deployments
-
Enable HTTPS: Always use HTTPS in production. Klutch.sh provides automatic HTTPS for custom domains.
-
Implement Health Checks: Add a
/healthendpoint that returns a 200 status code when healthy. -
Use Environment Variables: Store all configuration in environment variables, never hardcode secrets.
-
Monitor Performance: Regularly check logs and metrics in the Klutch.sh dashboard.
-
Optimize Binary Size: Use build flags like
-ldflags="-s -w"to reduce executable size. -
Implement Graceful Shutdown: Handle SIGTERM signals to close connections cleanly.
-
Use CORS Headers: Implement CORS properly if serving cross-origin requests.
-
Add Request Logging: Log all requests for debugging and monitoring purposes.
-
Set Timeouts: Configure appropriate read/write timeouts for requests.
-
Version Your Application: Use Git tags and version information for tracking deployments.
Resources and Further Reading
- Go Programming Language
- Go net/http Documentation
- Nixpacks Documentation
- Go GitHub Repository
- Effective Go Guide
- Klutch.sh Platform
Conclusion
Deploying net/http applications on Klutch.sh is straightforward and flexible. Whether you choose the Nixpacks deployment method for simplicity or the Docker method for complete control, Klutch.sh provides the tools and infrastructure needed for reliable, scalable production deployments. With support for environment variables, custom domains, and integrated monitoring, you can focus on building efficient APIs and web servers while Klutch.sh handles the deployment and scaling.
For additional support or questions about deploying your net/http application, visit the Klutch.sh website or consult the net/http documentation.