Adding a Go Service to DDEV

How to run a Go HTTP service alongside your existing PHP (or any other) app in DDEV, with nginx proxying specific routes to it.

This is useful when you want to gradually introduce Go into an existing project — for example, serving certain API endpoints from a faster runtime while everything else continues through your primary stack.

DDEV Docker Compose service

Create .ddev/docker-compose.go.yaml

services:
  go:
    image: golang:1.24-alpine
    container_name: ddev-${DDEV_SITENAME}-go
    working_dir: /app/api-go
    volumes:
      - ../api-go:/app/api-go:cached
    command: go run main.go
    environment:
      - GOPATH=/go
      - GOCACHE=/tmp/go-cache
    expose:
      - "8080"
    labels:
      com.ddev.site-name: ${DDEV_SITENAME}
      com.ddev.approot: ${DDEV_APPROOT}
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:8080/api/go/ping || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s

The labels are required — DDEV uses them to identify the container as part of your project.

The Go service

Create a minimal Go HTTP server at api-go/main.go:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /api/go/ping", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintln(w, `{"message":"pong from Go"}`)
    })

    log.Println("Go service listening on :8080")
    log.Fatal(http.ListenAndServe("0.0.0.0:8080", mux))
}

Initialize the module (one-time setup, the resulting go.mod gets committed):

ddev restart
ddev exec -s go go mod init api-go

Nginx routing

By default, DDEV’s nginx sends all requests to your primary webserver. To route specific paths to the Go container, add a location block to your nginx config.

Create or edit .ddev/nginx_full/nginx-site.conf — if you don’t have one yet, copy the default first:

ddev exec cat /etc/nginx/sites-enabled/nginx-site.conf > .ddev/nginx_full/nginx-site.conf

Then add this block before any catch-all location that would match the same prefix:

# Go API routes — proxied to Go service container.
location /api/go/ {
    proxy_pass http://go:8080;
    proxy_http_version 1.1;
    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;
}

Note: go in proxy_pass http://go:8080 is the service name from docker-compose.go.yaml. Nginx resolves it via Docker’s internal DNS.

Start and verify

ddev restart
curl -s https://your-site.ddev.site/api/go/ping
{"message":"pong from Go"}

The Go service handles /api/go/* routes. Everything else continues through your existing stack as before.

Running Go commands

Since Go runs inside the container, use ddev exec -s go to run commands:

ddev exec -s go go test ./...
ddev exec -s go go build -o /tmp/app main.go