Layer 4 Load Balancing Using NGINX and Docker
This post will guide you through how to use NGINX and Docker for setting up a Layer 4 (transport layer) load balancer. We want to balance the incoming TCP traffic into two servers using NGINX as load balancer. We’ll also make a custom NGINX image that goes along with the configuration inside nginx.conf
and our choice is Docker Compose for the orchestration.
Step 1: Initialize Go Projects
First, let’s create two Go projects for the TCP server and client respectively.
The project structure should be following:
├── client
│ ├── Dockerfile
│ ├── go.mod
│ └── main.go
├── docker
│ └── docker-compose.yaml
├── nginx
│ ├── Dockerfile
│ └── nginx.conf
└── server
├── Dockerfile
├── go.mod
└── main.go
1.1 TCP Server
Create a directory structure for the TCP server:
mkdir server
cd server
go mod init tcp-server
Now, create the main.go
file with the following content:
package main
import (
"fmt"
"net"
"os"
)
func main() {
port := "8000"
hostname, err := os.Hostname()
listener, err := net.Listen("tcp", ":"+port)
if err != nil {
panic(err)
}
fmt.Printf("Golang TCP server listening on port %sn", port)
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Connection error:", err)
continue
}
go func(c net.Conn) {
defer c.Close()
c.Write([]byte("Hello from " + hostname + " on port " + port + "n"))
}(conn)
}
}
This is a server listening on port 8000 that responds with a message and the hostname of the current server.
Now create a Dockerfile
for the server:-
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod ./
RUN go mod tidy
COPY . .
RUN go build -o tcp-server .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/tcp-server .
EXPOSE 8000
CMD ["./tcp-server"]
1.2 TCP Client Proje
Create a TCP client:
mkdir -p client
cd client
go mod init tcp-client
Now, create the main.go
file with the following content:
package main
import (
"bufio"
"fmt"
"net"
)
func main() {
port := "8080"
address := "localhost:" + port // NGINX LB port
conn, err := net.Dial("tcp", address)
if err != nil {
fmt.Printf("Error connecting to %s: %vn", address, err)
return
}
defer conn.Close()
fmt.Printf("Connected to %sn", address)
// Read response
message, err := bufio.NewReader(conn).ReadString('n')
if err != nil {
fmt.Println("Error reading response:", err)
return
}
fmt.Printf("Received: %sn", message)
}
This client connects to the NGINX load balancer at port 8080
and reads it.
Step 2: Setting Up NGINX to Use Layer 4 Load Balancing
NGINX supports Layer 4 load balancing using the stream
module. Create a directory structure for NGINX:
mkdir -p nginx
cd nginx
Create the nginx.conf
file with the following content:
worker_processes 1;
events {
worker_connections 1024;
}
stream {
upstream backend {
server server-1:8000;
server server-2:8000;
}
server {
listen 8080;
proxy_pass backend;
}
}
This configuration defines an upstream block with two servers (server-1
and server-2
) running on port 8000
. The server
block listens on port 8080
and forwards incoming TCP connections to the upstream servers.
Next, create a Dockerfile
for NGINX:
FROM nginx:alpine
RUN apk add --no-cache nginx
# Create directory for custom config
RUN mkdir -p /etc/nginx
# Copy your nginx.conf
COPY nginx.conf /etc/nginx/nginx.conf
# Create required dirs
RUN mkdir -p /run/nginx
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
Step 3: Build and Push The Docker Images
- Build the Docker images:
docker build -t olymahmudmugdho/go-tcp-server -f server/Dockerfile server/
docker build -t olymahmudmugdho/tcp-nginx -f nginx/Dockerfile nginx/
- Push the Docker images:
docker push olymahmudmugdho/go-tcp-server
docker push olymahmudmugdho/tcp-nginx
Step 4: Use Docker Compose to Orchestrate the Setup
Create a docker-compose.yaml
file in the root directory:
services:
nginx:
image: olymahmudmugdho/tcp-nginx
container_name: nginx
ports:
- "8080:8080"
depends_on:
- server-1
- server-2
server-1:
image: olymahmudmugdho/go-tcp-server
container_name: server-1
hostname: server-1
server-2:
image: olymahmudmugdho/go-tcp-server
container_name: server-2
hostname: server-2
This configuration defines three services:
-
nginx
: The NGINX load balancer. -
server-1
andserver-2
: Two instances of the TCP server.
Step 5: Run
- Run the setup using Docker Compose:
docker compose -f docker/docker-compose.yaml up -d
-
Test the setup:
Run the client to send requests to the load balancer:
cd client
go run main.go
go run main.go
You should see responses alternating between server-1
and server-2
, indicating that the load balancer is working correctly.
Conclusion
This post provides a guide to creating a Layer 4 load balancer with Docker and NGINX to distribute TCP requests between two different servers. NGINX stream
module, Docker Compose.