How to Use Caddy To Serve Static Vite React Frontend and a Gunicorn Django Backend With Docker Locally

1. The problem I’m having:

Hello! I am trying to test Caddy locally before deploying to Railway. I would like to have Caddy serve statically built files from Vite React while “forwarding” REST API requests such as /api/v1/* requests to my Django Backend that is served with Gunicorn. For local development I am using Docker compose but I am not using volumes because I know this is not supported on Railway.

I am getting an error in the networking tab of my browser that says “CORS Missing Allow Origin” and for a POST request that says “NS_ERROR_DOM_BAD_URI” (see image). I also get a 502 Bad Gateway. This was triggered when I click a button on my frontend that calls a fetch request.

I believe my error is in the Caddyfile because of these errors as well as the fact that I am not getting any information in the Django Docker container.

I am a noob at proxying and tools such as Caddy and Ngnix but do you think you could give me some pointers?

2. Error messages and/or full log output:

{
   "level":"error",
   "ts":1710270972.0806715,
   "logger":"http.log.error",
   "msg":"dial tcp 127.0.0.1:8000: connect: connection refused",
   "request":{
      "remote_ip":"172.19.0.1",
      "remote_port":"44746",
      "client_ip":"172.19.0.1",
      "proto":"HTTP/1.1",
      "method":"OPTIONS",
      "host":"127.0.0.1:4173",
      "uri":"/api/v1/register",
      "headers":{
         "Access-Control-Request-Method":[
            "POST"
         ],
         "Connection":[
            "keep-alive"
         ],
         "Access-Control-Request-Headers":[
            "content-type"
         ],
         "Referer":[
            "http://localhost:4173/"
         ],
         "Origin":[
            "http://localhost:4173"
         ],
         "Sec-Fetch-Mode":[
            "cors"
         ],
         "User-Agent":[
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0"
         ],
         "Accept-Language":[
            "en-US,en;q=0.5"
         ],
         "Sec-Fetch-Dest":[
            "empty"
         ],
         "Sec-Fetch-Site":[
            "cross-site"
         ],
         "Accept":[
            "*/*"
         ],
         "Accept-Encoding":[
            "gzip, deflate, br"
         ]
      }
   },
   "duration":0.001712024,
   "status":502,
   "err_id":"x7s5scgj0",
   "err_trace":"reverseproxy.statusError (reverseproxy.go:1267)"
}

3. Caddy version:

caddy:2.7.6-alpine

4. How I installed and ran Caddy:

Via a Dockerfile though I using Docker compose to run the Dockerfile

a. System environment:

Docker / caddy:2.7.6-alpine

b. Command:

I am only formatting the Caddyfile with a Docker RUN command:

RUN caddy fmt --overwrite /etc/caddy/Caddyfile

c. Service/unit/compose file:

Dockerfile

# Build React Image
FROM node:latest AS frontend

WORKDIR /frontend

# React / Yarn / Node stuff
ARG VITE_SERVER_URL

COPY ./frontend/package*.json ./frontend/yarn.lock ./

RUN yarn && yarn install

COPY ./frontend .

RUN yarn build


# Caddy Build Image
FROM caddy:2.7.6-alpine

ARG PORT
ARG CADDY_BACKEND_HOST

# Copy Caddyfile
COPY /caddy-server/Caddyfile /etc/caddy/Caddyfile

# Copy the built application from the previous stage
COPY --from=frontend /frontend/dist /srv
COPY --from=frontend /frontend/dist /var/www/html

RUN caddy fmt --overwrite /etc/caddy/Caddyfile

Environment variables:

PORT=4173
CADDY_BACKEND_HOST=127.0.0.1:8000
VITE_SERVER_URL=http://127.0.0.1:4173

d. My complete Caddy config:

{
	debug
}


:{$PORT} {
	root * /var/www/html

	header {
        Access-Control-Allow-Origin *
        Access-Control-Allow-Credentials true
        Access-Control-Allow-Methods *
        Access-Control-Allow-Headers *
        defer
    }
	reverse_proxy /api/* localhost:8000
	file_server
}

CORS is typically an application layer issue. You should configure somekind of CORS middleware in your Django backend. That’s usually a pretty trivial thing to do on the backend.

FWIW, I’d write your config like this:

:{$PORT} {
	encode gzip

	handle /api* {
		reverse_proxy localhost:8000
	}

	handle {
		root * /var/www/html
		try_files {path} /index.html
		file_server
	}
}

Small thing, I’d just always use port 80 inside the container (if you’re not having Caddy handle TLS). You can set up a port mapping from whatever port to 80 in your Docker setup. That way it’s predictable how to reach your Caddy container from another container if necessary.

The problem here is that localhost inside a container means “this same container”. If you need to proxy to another container, you need to use that container’s name.

So like reverse_proxy django:8000 or whatever.

Hello Francis, thank you for your reply. I had realized that I was getting the 502 issue because I had forgotten to add a network to my Caddy service in my Docker compose file and, as you had mentioned, I should use the container’s name. I did not need to change anything in my Django settings. I did however update my host for the reverse_proxy to reflect the Django docker image. I have updated my Caddyfile according to recommendations.

Thank you

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.