Go ist eine kompilierte Sprache, die statisch gelinkte Binaries erzeugt. Go-Webserver sind schnell, ressourcenschonend und einfach zu deployen - ein einzelnes Binary ohne externe Abhängigkeiten.

Go installieren

Offizielle Installation

# Download
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz

# Entpacken
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz

# PATH setzen
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
echo 'export GOPATH=$HOME/go' >> ~/.bashrc
echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.bashrc
source ~/.bashrc

# Prüfen
go version

Debian/Ubuntu

apt install golang-go

Einfacher Webserver

main.go

// main.go

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"
)

type Response struct {
    Message string `json:"message"`
    Status  string `json:"status"`
}

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/health", healthHandler)
    http.HandleFunc("/api/data", apiHandler)

    log.Printf("Server startet auf Port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(Response{
        Message: "Hello from Go!",
        Status:  "ok",
    })
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"status": "healthy"})
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    data := map[string]interface{}{
        "items": []string{"item1", "item2", "item3"},
        "count": 3,
    }
    json.NewEncoder(w).Encode(data)
}

Mit Router (Chi)

// main.go

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()

    // Middleware
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.RealIP)

    // Routes
    r.Get("/", homeHandler)
    r.Get("/health", healthHandler)

    r.Route("/api", func(r chi.Router) {
        r.Get("/users", getUsersHandler)
        r.Post("/users", createUserHandler)
        r.Get("/users/{id}", getUserHandler)
    })

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    log.Printf("Server startet auf :%s", port)
    http.ListenAndServe(":"+port, r)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(map[string]string{"message": "Hello"})
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

func getUsersHandler(w http.ResponseWriter, r *http.Request) {
    // Users zurückgeben
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    // User erstellen
}

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    userID := chi.URLParam(r, "id")
    json.NewEncoder(w).Encode(map[string]string{"id": userID})
}

Binary kompilieren

Für Linux

# Auf Linux
go build -o myapp main.go

# Cross-Compile von macOS/Windows
GOOS=linux GOARCH=amd64 go build -o myapp main.go

Optimiertes Build

# Mit Optimierungen
CGO_ENABLED=0 GOOS=linux go build \
    -ldflags="-w -s" \
    -o myapp main.go

# -w: Keine DWARF Debug-Info
# -s: Keine Symbol-Tabelle

Mit Version

VERSION=$(git describe --tags --always)
BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S')

go build -ldflags="-X main.Version=$VERSION -X main.BuildTime=$BUILD_TIME" -o myapp
// In main.go
var (
    Version   string
    BuildTime string
)

func versionHandler(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(map[string]string{
        "version":    Version,
        "build_time": BuildTime,
    })
}

Deployment

Binary kopieren

# Auf Build-Server
go build -o myapp

# Auf Produktionsserver kopieren
scp myapp user@server:/var/www/myapp/

Verzeichnisstruktur

mkdir -p /var/www/myapp
mkdir -p /var/log/myapp

# Binary
cp myapp /var/www/myapp/

# Berechtigungen
chown -R www-data:www-data /var/www/myapp
chmod +x /var/www/myapp/myapp

Systemd-Service

myapp.service

# /etc/systemd/system/myapp.service

[Unit]
Description=My Go Application
After=network.target

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp

Environment=PORT=8080
Environment=GIN_MODE=release
EnvironmentFile=-/var/www/myapp/.env

ExecStart=/var/www/myapp/myapp
ExecReload=/bin/kill -s HUP $MAINPID

Restart=on-failure
RestartSec=5

# Resource Limits
LimitNOFILE=65535
MemoryMax=512M

# Security
NoNewPrivileges=true
PrivateTmp=true

StandardOutput=append:/var/log/myapp/app.log
StandardError=append:/var/log/myapp/error.log

[Install]
WantedBy=multi-user.target

Service starten

systemctl daemon-reload
systemctl enable myapp
systemctl start myapp
systemctl status myapp

Nginx Reverse Proxy

# /etc/nginx/sites-available/myapp

upstream goapp {
    server 127.0.0.1:8080;
    keepalive 64;
}

server {
    listen 80;
    server_name api.example.de;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.example.de;

    ssl_certificate /etc/letsencrypt/live/api.example.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.de/privkey.pem;

    access_log /var/log/nginx/myapp-access.log;
    error_log /var/log/nginx/myapp-error.log;

    location / {
        proxy_pass http://goapp;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    location /static/ {
        alias /var/www/myapp/static/;
        expires 30d;
    }
}
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Umgebungsvariablen

.env-Datei

# /var/www/myapp/.env

PORT=8080
DATABASE_URL=postgres://user:pass@localhost/myapp
REDIS_URL=redis://localhost:6379
LOG_LEVEL=info

In Go laden

package main

import (
    "os"

    "github.com/joho/godotenv"
)

func init() {
    godotenv.Load()
}

func main() {
    dbURL := os.Getenv("DATABASE_URL")
    // ...
}

Graceful Shutdown

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    srv := &http.Server{
        Addr:         ":8080",
        Handler:      setupRoutes(),
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }

    // Server in Goroutine starten
    go func() {
        log.Println("Server startet auf :8080")
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    // Auf Signal warten
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("Server wird heruntergefahren...")

    // Graceful Shutdown mit Timeout
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("Shutdown error: %v", err)
    }

    log.Println("Server beendet")
}

Docker

Dockerfile

# Build Stage
FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /myapp

# Production Stage
FROM alpine:3.19

RUN apk --no-cache add ca-certificates

WORKDIR /app
COPY --from=builder /myapp .

USER nobody:nobody

EXPOSE 8080
CMD ["./myapp"]

docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - DATABASE_URL=postgres://user:pass@db/myapp
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Deployment-Skript

#!/bin/bash
# deploy.sh

set -e

SERVER="user@server"
APP_PATH="/var/www/myapp"
APP_NAME="myapp"

echo "Building..."
CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o ${APP_NAME}

echo "Copying to server..."
scp ${APP_NAME} ${SERVER}:${APP_PATH}/${APP_NAME}.new

echo "Deploying..."
ssh ${SERVER} << EOF
    cd ${APP_PATH}
    mv ${APP_NAME}.new ${APP_NAME}
    sudo systemctl restart ${APP_NAME}
EOF

echo "Done!"
rm ${APP_NAME}

Zusammenfassung

| Komponente | Funktion | |------------|----------| | go build | Binary kompilieren | | Systemd | Process Management | | Nginx | Reverse Proxy |

| Flag | Funktion | |------|----------| | -ldflags="-w -s" | Kleineres Binary | | CGO_ENABLED=0 | Statisch gelinkt | | GOOS=linux | Cross-Compile |

| Datei | Funktion | |-------|----------| | /var/www/myapp/myapp | Binary | | /etc/systemd/system/myapp.service | Service | | .env | Umgebungsvariablen |

Fazit

Go-Anwendungen sind extrem einfach zu deployen. Ein einzelnes Binary ohne Abhängigkeiten macht das Deployment unkompliziert. Go-Server sind performant und ressourcenschonend. Graceful Shutdown ermöglicht Zero-Downtime-Deployments. Mit Docker lassen sich Go-Apps containerisieren. Für APIs und Microservices ist Go eine ausgezeichnete Wahl.