Overview – Caddy, Docker, React, Node.js
In this blog post, I’m going to share how, you can setup a reverse proxy to host a React and Node.js app from a single server – all while having automatic SSL. Here are the technologies used:
- Docker
- Caddy
- A React front-end
- A Node.js back-end
Hosting?
You’ve built your React front-end and a Node.js back-end. It all works beautifully using Vite
‘s local hosting options. You’d now like embark on hosting this publicly and ensure you have SSL certificates – which are automatically renewed.
This means, you’d need to solve for a few aspects:
- Hosting the front-end application
- Hosting the back-end server
- Ensuring the above have SSL certs
Cloud hosting solutions are ubiquitous these days – DigitalOcean’s AppPlatform, Netlify, Vercel, etc. These are convenient and provide a relatively quick way to host your services, however, there is an alternative – hosting these yourself on a virtual private server (VPS).
The self-hosting route takes a bit more effort and your application may not easily scale, but for the project I had in mind, these weren’t a big enough blocker. Hence, if I opted for the latter option.
Self-hosting
Solving the reverse proxy problem
Why do I need a reverse proxy?
A reverse proxy is the system/tool that can redirect all requests that are made to a single public IP address to specific applications internally.
For example:
Your VPS has a public IP of 172.14.1.129. When you setup a DNS record to point to that public IP, all traffic will be routed there. There’s no distinguishing between the front-end and back-end at this point. Herein lies the value of a reverse proxy. We can setup the following DNS records:
- Front-end (React):
myapp.com
->172.14.1.129
- Back-end (Node.js):
api.myapp.com
->172.14.1.129
Then, the reverse proxy will do the following:
- Front-end (React):
myapp.com
->172.14.1.129
-> Reverse Proxy -> Redirect to internal React app IP address - Back-end (Node.js):
api.myapp.com
->172.14.1.129
-> Reverse Proxy -> Redirect to internal Node.js app IP address
Choosing your reverse proxy tool
In a previous blog post, I used a tool called Traefik and it worked well for what I needed at the time.
Instead of using Traefik again, I stumbled upon Caddy. I was intrigued by the minimal configuration required so decided to give it a try.
Using Caddy
To use Caddy
, you will need the following:
- A
Caddyfile
where you define how you’d like your reverse proxy to function - Additional config within your
docker-compose.yml
file
The project directory is as follows:
- root
- client/
Dockerfile
Dockerfile
(forserver
– Nodejs) – thinking about this more, I should have created a separate directory to house all Nodejs/server codeCaddyfile
docker-compose.yml
- client/
Here’s an example of the Caddyfile
I used:Caddyfile
:
// Back-end API (Node.js)
api.example.com {
reverse_proxy server:5000 { // server - referes to the docker service name
header_down Strict-Transport-Security "max-age=31536000"
}
}
// Front-end app (React)
example.com {
root * /srv
try_files {path} /index.html
file_server
}
This docker-compose.yml
file that has the following services:
server
– the Node.js serverclient
– the React front-endcaddy
– Caddy used for reverse proxy
version: "3.7"
services:
client:
container_name: client
build:
context: ./client
dockerfile: Dockerfile
networks:
- network
volumes:
- app-dist:/app/client/dist # Mount the app-dist volume to the dist directory
server:
container_name: server
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:5000"
restart: always
environment:
"NODE_ENV": "production"
networks:
- network
# Run the caddy server
caddy:
image: caddy/caddy:2.7.5-alpine
container_name: caddy-service
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- app-dist:/srv # Mount the app-dist volume to the Caddy service
- caddy_data:/data
- caddy_config:/config
networks:
- network
depends_on:
- server
- client
volumes:
app-dist:
caddy_data:
caddy_config:
networks:
network:
driver: bridge
Here is the Dockerfile
used for the client
(React) front-end:
# Use an official Node.js image as the base image
FROM node:20 as builder
# Set the working directory
WORKDIR /app/client
# Copy package.json and package-lock.json to the working directory
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the entire client directory to the working directory
COPY . ./
# Build the production version of the React app
RUN npm run build
# This is important as it's referenced in the docker-file
VOLUME /app/client/dist
All that’s left for you to do is run docker-compose up -d
and if all goes well, you’ll have both your React and Node.js app with automatic SSL certificates hosted on a single VPS.
Troubleshooting Caddy
Caddy SSL not working?
Did you delete caddy_data
?
caddy_data
contains your SSL certificates, to avoid rate limits from Lets Encrypt, avoid deleting this volume frequently
Reverse proxy isn’t working?
Check which port your service is running on
- Make sure you’re using the docker internal service IP (e.g. for my
server
I’m using port 5000) in yourCaddyfile
Ensure you’re using the service name
- Make sure you’re referring to the docker service name in your
Caddyfile
. Notice I’m usingserver:5000
Conclusion
Using Caddy as a reverse proxy for a React and Node.js app, all hosted on a single server took minimal effort and it works! The automatic SSL that Caddy provides is invaluable for anyone self-hosting secure apps.