Skip to content

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:

Terminal window
mkdir my-net-http-app
cd my-net-http-app
go mod init my-net-http-app

Step 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:

Terminal window
go run main.go

Visit http://localhost:8080 in your browser or use curl to test:

Terminal window
curl http://localhost:8080/
curl http://localhost:8080/api/hello
curl http://localhost:8080/health

Step 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

  1. Ensure your project has the following files in the root directory:

    • go.mod and go.sum (generated by go mod init and go mod tidy)
    • main.go (your application entry point)
    • Any additional source files and packages
  2. Push your net/http application to a GitHub repository:

Terminal window
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/your-username/my-net-http-app.git
git push -u origin main
  1. Log in to your Klutch.sh dashboard.

  2. Create a new project by selecting your desired organization and naming your project (e.g., “My net/http API”).

  3. 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 main or master)
    • 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
  4. 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
  5. 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.go
START_COMMAND=./bin/app

These 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

  1. Create a Dockerfile in your project root with a multi-stage build for optimal image size:
# Stage 1: Build the application
FROM golang:1.20-alpine AS builder
WORKDIR /app
# Copy go.mod and go.sum
COPY go.mod go.sum ./
# Download dependencies
RUN go mod download
# Copy application source code
COPY . .
# Build the binary with optimizations
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o net-http-app main.go
# Stage 2: Create minimal runtime image
FROM alpine:latest
# Install ca-certificates for HTTPS requests
RUN apk --no-cache add ca-certificates
WORKDIR /app
# Copy the built binary from builder stage
COPY --from=builder /app/net-http-app .
# Expose port (must match your net/http app's port)
EXPOSE 8080
# Set the default environment port variable
ENV PORT=8080
# Run the application
CMD ["./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.

  1. Push your code to GitHub (including the Dockerfile):
Terminal window
git add Dockerfile
git commit -m "Add Dockerfile for production deployment"
git push
  1. Log in to your Klutch.sh dashboard.

  2. Create a new project (or use an existing one).

  3. 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
  4. Click “Create” to deploy. Klutch.sh will automatically:

    • Detect your Dockerfile
    • Build your Docker image
    • Deploy the containerized application to your Klutch.sh infrastructure
  5. 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: Build
FROM 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=dev
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w -X main.Version=${VERSION}" \
-o net-http-app main.go
# Stage 2: Runtime
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/net-http-app .
EXPOSE 8080
ENV 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=8080
APP_ENV=production
LOG_LEVEL=info
DATABASE_URL=postgres://user:password@db.example.com/mydb
API_KEY=your-api-key-here
SECRET_KEY=your-secret-key-here
CORS_ORIGINS=https://yourdomain.com

Reading 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 requests
func 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 tokens
func 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 helper
func 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:

  1. In the Klutch.sh dashboard, navigate to your application settings.

  2. Find the Domains or Custom Domain section.

  3. Add your custom domain (e.g., api.mycompany.com).

  4. 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
  5. Verify the domain ownership through Klutch.sh’s verification process.

  6. 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:

  1. In the Klutch.sh dashboard, view your application’s Logs to troubleshoot issues.

  2. Check Metrics for CPU usage, memory consumption, and request throughput.

  3. Set up Alerts to be notified of performance issues or errors.

  4. 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.mod and go.sum files 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 PORT environment 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

  1. Enable HTTPS: Always use HTTPS in production. Klutch.sh provides automatic HTTPS for custom domains.

  2. Implement Health Checks: Add a /health endpoint that returns a 200 status code when healthy.

  3. Use Environment Variables: Store all configuration in environment variables, never hardcode secrets.

  4. Monitor Performance: Regularly check logs and metrics in the Klutch.sh dashboard.

  5. Optimize Binary Size: Use build flags like -ldflags="-s -w" to reduce executable size.

  6. Implement Graceful Shutdown: Handle SIGTERM signals to close connections cleanly.

  7. Use CORS Headers: Implement CORS properly if serving cross-origin requests.

  8. Add Request Logging: Log all requests for debugging and monitoring purposes.

  9. Set Timeouts: Configure appropriate read/write timeouts for requests.

  10. Version Your Application: Use Git tags and version information for tracking deployments.


Resources and Further Reading


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.