Configuring Nginx as a Reverse Proxy for a Go App in Docker.
If you have ever worked with Nginx, you know that one of its main use cases is to place it at the front of your application and use it as a reverse proxy to enhance security, performance, and scalability — among other benefits. In this guide, we will configure a Docker architecture where a Go application communicates with the outside world through an Nginx server, with each component running in its own container.
I’m assuming you know how to use Docker and you are also familiar with Docker Compose. Otherwise, this guide might be a bit challenging to follow, so I recommend reviewing the basics of Docker and Docker Compose before proceeding.
Project Organization
Let’s start with the project organization. For this example, I have a simple REST API written in Go with only one endpoint. I’ve simplified it since this guide focuses more on the Docker setup rather than on building APIs in Go.
nginx-go/
├── compose.yml
├── go-app/
│ ├── cmd/
│ │ └── main.go
│ └── Dockerfile
└── nginx/
├── Dockerfile
└── nginx.conf
Go App Configuration
We’ll take a look at the go-app/cmd/main.go
first
package main
import (
"fmt"
"log"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Nginx server as a reverse proxy!")
}
func main() {
http.HandleFunc("/", indexHandler)
log.Fatal(http.ListenAndServe(":3000", nil))
}
Basically, it contains the configuration for starting the app and handling incoming requests properly. As mentioned, it’s a simplified version, with all the code located in the main.go
file, which we would typically avoid in more complex scenarios. However, since the focus here is on Docker, we won’t go deep into the Go code.
Taking a look at the go-app/Dockerfile
FROM golang:1.21.3 AS builder
WORKDIR /app
COPY /cmd/main.go ./cmd/
RUN go mod init go-app
RUN CGO_ENABLED=0 GOOS=linux go build -o /go-app ./cmd/main.go
FROM scratch
WORKDIR /
COPY --from=builder /go-app .
EXPOSE 3000
CMD [ "/go-app" ]
The purpose of this Dockerfile is to build the Go binary to run on a lightweight image. It’s as simple as that.
Note that port 3000 in the Go container is exposed within the network, but we won’t expose it to the outside.
Nginx configuration
Here’s where things start to get more interesting. To place Nginx in front of the Go application, we need to configure the Dockerfile for Nginx (as usual) and also configure the nginx.conf
file to reroute the requests received by the Nginx server to the Go application running on port 3000. Let’s look at the Dockerfile for Nginx.
nginx/Dockerfile
FROM nginx:latest
EXPOSE 80
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
It typically builds the image and starts the server exposing port 80.
nginx/nginx.conf
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://go-app:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
The nginx.conf
file is the trickiest part, as it specifies where to send the requests received on port 80. Here, we set proxy_pass
to route the requests to http://go-app:3000
. Since Docker containers know the names of other containers (due to built-in DNS resolution), they can communicate with each other using just the name and port (http://go-app:3000).
Docker Compose Configuration
We’re using Docker Compose to automate the process of setting up and running the containers. In this compose.yml
file, we are configuring the Go app and the Nginx server to run on the same go-network
, which is also defined in the compose file. In the configuration for the Nginx container, we're mapping port 8080 from our system to port 80 of the Nginx container, so we can easily access it from localhost:8080
.
We also create a volume to map the nginx/
folder in our working directory to the /etc/nginx/conf.d/
folder inside the container, so our nginx.conf
file can be placed inside the Nginx container.
compose.yml
version: '3'
services:
app:
build:
context: go-app
container_name: go-app
networks:
- go-network
nginx:
build:
context: nginx
container_name: nginx
volumes:
- ./nginx:/etc/nginx/conf.d/
networks:
- go-network
ports:
- "8080:80"
depends_on:
- app
networks:
go-network:
driver: bridge
Running Everything
With all that done, we can now run these containers in detached mode and check if everything went well. We will open the terminal and run the following command:
docker compose up -d
No errors occurred, so this looks promising. Now, we can open the browser and try to access localhost:8080
to see the result.
Great! We can see the response from the index endpoint that we configured. Since the Go app is running on port 3000, we know that Nginx is correctly redirecting from its own port 80, which we bound to our 8080. This means everything is working as expected.
Conclusion
I hope this guide has given you insight into how you can easily configure your Docker environment to use Nginx as a reverse proxy for a Go app. I’ve chosen to use Go as an example, but the configuration structure is almost the same for any app written in any language. So, if you want, this setup can be adapted without much hassle.
You can check the source code for this guide on Github.