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:
goinproxy_pass http://go:8080is 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