I have an Express api and an Nginx together in a Docker Container. I want the Nginx to reverse proxy regular requests to the API, but I want it to serve the static files like images directly.
But I can't for the life of me seem to figure out the exact URI that I need to use here and the images keep 404-ing. Here's the docker-compose:
services:
api:
build:
context: .
dockerfile: Dockerfile
container_name: api
nginx-proxy:
build:
context: .
dockerfile: Dockerfile.nginx
container_name: nginx-proxy
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- api
And here's the nginx.conf
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://api:3000;
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;
}
location /images/ {
alias /public/images/;
# THIS PART JUST DOESN'T WORK
}
}
I've tried multiple URIs but I can't figure out the logic of this. How does the Nginx image access the files from the Express image? This is the error I get:
2024-01-07 00:00:00 nginx-proxy | 2024/01/06 00:00:00 [error] 30#30: *4 open() "/public/images/test.png" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /images/test.png HTTP/1.1", host: "localhost:8080", referrer: "http://localhost:2222/"
Edit: I should add that I did copy the images folder in the API Dockerfile, they are served when I query them through the API.
You haven't shared with us the contents of your nginx Dockerfile, but it looks like you're never placing any content into the /public/images
directory in the nginx container.
Rather than building the images into the container image, we can bind mount a local images
directory into the container at runtime. This lets us use the stock nginx image, rather than needing to build our own.
Here's a slightly modified version of your Dockerfile that seems to work just fine:
services:
angles-api:
build:
context: api
healthcheck:
test: ["CMD", "curl", "-sSf", "http://localhost:3000"]
interval: "5s"
retries: 3
nginx-proxy:
image: docker.io/nginx:mainline
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./images:/public/images
depends_on:
angles-api:
condition: service_healthy
Where:
api/Dockerfile
contains:
FROM docker.io/alpine:latest
USER root
RUN apk add curl
RUN curl -sSfL -o /tmp/whoami.tar.gz https://github.com/traefik/whoami/releases/download/v1.10.1/whoami_v1.10.1_linux_amd64.tar.gz
RUN tar -C /usr/local/bin -xf /tmp/whoami.tar.gz whoami
ENV WHOAMI_PORT_NUMBER=3000
CMD ["/usr/local/bin/whoami"]
./nginx.conf
contains:
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://angles-api:3000;
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;
}
location /images/ {
alias /public/images/;
autoindex on;
}
}
And ./images
contains:
total 160
-rw-r--r-- 1 lars lars 161352 Jan 6 21:15 uncle-deadly.png
With the above configuration, a request to localhost:8080/images/
goes to the /public/images
directory in the nginx container:
$ curl localhost:8080/images/
<html>
<head><title>Index of /images/</title></head>
<body>
<h1>Index of /images/</h1><hr><pre><a href="../">../</a>
<a href="uncle-deadly.png">uncle-deadly.png</a> 07-Jan-2024 02:15 161352
</pre><hr></body>
</html>
A request for any path not under /images
goes to the API container:
$ curl localhost:8080/
Hostname: 8c7638b8d4c2
IP: 127.0.0.1
IP: 192.168.144.2
RemoteAddr: 192.168.144.3:39468
GET / HTTP/1.1
Host: localhost
User-Agent: curl/8.0.1
Accept: */*
Connection: close
X-Forwarded-For: 192.168.144.1
X-Forwarded-Proto: http
X-Real-Ip: 192.168.144.1
And:
$ curl localhost:8080/foo/bar
Hostname: 8c7638b8d4c2
IP: 127.0.0.1
IP: 192.168.144.2
RemoteAddr: 192.168.144.3:40144
GET /foo/bar HTTP/1.1
Host: localhost
User-Agent: curl/8.0.1
Accept: */*
Connection: close
X-Forwarded-For: 192.168.144.1
X-Forwarded-Proto: http
X-Real-Ip: 192.168.144.1
Note that in the above compose.yaml
, we're using the long form of the depends_on
option so that the nginx container doesn't start up until the api container is healthy. The older short form of the depends_on
option is effectively useless, since it doesn't know anything about the state of the application running inside the container (so it doesn't prevent a dependent container from trying to make requests before the target container is ready).
All of the files referenced in this answer can be found online here.