Docker + nginx + HTTPS setup for Company Recommender
This document contains a ready-to-use Docker deployment for your FastAPI backend and Angular frontend, plus an NGINX reverse proxy. It also includes instructions for obtaining Let’s Encrypt certificates with Certbot (manual step).
Important: I placed all files and config examples below. Follow the numbered steps in Deployment to build, obtain certificates, and run in production.
Files included
-
docker-compose.yml
— orchestrates backend, frontend, nginx, and certbot (optional) -
backend/Dockerfile
— builds your FastAPI app (uvicorn) -
frontend/Dockerfile
— builds Angular production bundle and serves using nginx -
nginx/nginx.conf
— main nginx config with HTTP -> HTTPS redirect -
nginx/conf.d/immai.acintia.com.conf
— site config (reverse proxy to backend + static hosting for frontend) -
README_DEPLOY.md
— deployment steps and Certbot instructions
docker-compose.yml
version: '3.8'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: company_recommender_backend
environment:
- PORT=8000
- OLLAMA_URL=https://immai.acintia.com
expose:
- "8000"
restart: unless-stopped
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: company_recommender_frontend
restart: unless-stopped
expose:
- "80"
nginx:
image: nginx:stable
container_name: company_recommender_nginx
ports:
- "80:80"
- "443:443"
depends_on:
- frontend
- backend
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./certs:/etc/letsencrypt/live:ro
- ./nginx/html:/usr/share/nginx/html:ro
restart: unless-stopped
certbot:
image: certbot/certbot
container_name: company_recommender_certbot
volumes:
- ./certs:/etc/letsencrypt/live
- ./nginx/html:/var/www/html
entrypoint: ''
command: "/bin/sh -c 'sleep infinity'"
restart: 'no'
networks:
default:
driver: bridge
Notes:
certbot
here is present to allow you to run one-off cert issuance commands using the certbot container (instructions below).
./certs
will hold the live certificate files after you create them (mounted read-only into nginx).
backend/Dockerfile
# backend/Dockerfile
FROM python:3.11-slim
WORKDIR /app
# system deps (if needed)
RUN apt-get update && apt-get install -y --no-install-recommends build-essential curl && rm -rf /var/lib/apt/lists/*
COPY backend/requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY backend/ .
ENV PORT=8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
Make sure
backend/requirements.txt
contains:fastapi
,uvicorn[standard]
,langgraph
,langchain-core
,langchain-community
,pydantic
, and any other packages yourmain.py
imports.
frontend/Dockerfile
(Angular)
# frontend/Dockerfile
# Build stage
FROM node:20 AS build
WORKDIR /app
COPY frontend/package*.json ./
RUN npm ci --legacy-peer-deps
COPY frontend/ .
RUN npm run build -- --configuration production
# Production stage
FROM nginx:stable
COPY --from=build /app/dist/ /usr/share/nginx/html/
# optional: copy a custom nginx conf for serving SPA (404 -> index.html handled by nginx conf)
COPY nginx/spa.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Assumes your Angular build output lands in
/app/dist/
(adjust if your project name differs;ng build
by default createsdist/<project-name>
).
nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
nginx/conf.d/immai.acintia.com.conf
# Redirect HTTP to HTTPS
server {
listen 80;
server_name immai.acintia.com;
root /var/www/html;
location /.well-known/acme-challenge/ {
alias /var/www/html/.well-known/acme-challenge/;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS server
server {
listen 443 ssl http2;
server_name immai.acintia.com;
ssl_certificate /etc/letsencrypt/live/immai.acintia.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/immai.acintia.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# Serve frontend static files
location / {
proxy_pass http://frontend:80;
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;
}
# API proxying
location /recommend {
proxy_pass http://backend:8000/recommend;
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 /chat {
proxy_pass http://backend:8000/chat;
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;
}
# optionally allow access to certbot challenge files served from nginx html
location ^~ /.well-known/acme-challenge/ {
alias /var/www/html/.well-known/acme-challenge/;
allow all;
}
}
The
proxy_pass
uses the Docker service namesfrontend
andbackend
defined indocker-compose.yml
so nginx communicates over the Docker network.
nginx/spa.conf
(used by frontend image)
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
README_DEPLOY.md — Deployment steps (summary)
-
Clone repo and place the backend and frontend project folders side-by-side with
docker-compose.yml
andnginx/
folder.
project-root/
├─ backend/ # your FastAPI app (main.py, requirements.txt)
├─ frontend/ # your Angular project (package.json, angular.json)
├─ nginx/
├─ docker-compose.yml
└─ certs/ # created after certbot
-
Build images and start containers (without certs yet):
docker compose up -d --build
-
Obtain Let’s Encrypt certificates using the
certbot
container + webroot method (run on the host):
-
Ensure DNS for
immai.acintia.com
points to the server IP. -
Ensure port 80 is reachable externally and not blocked by firewall.
Run this command (example using docker exec into certbot container):
# create the directory for challenge files
mkdir -p nginx/html/.well-known/acme-challenge
# run certbot interactively to obtain certs
docker run --rm -it \
-v "$(pwd)/certs:/etc/letsencrypt/live" \
-v "$(pwd)/nginx/html:/var/www/html" \
certbot/certbot certonly --webroot \
--webroot-path /var/www/html \
-d immai.acintia.com \
--email your-email@example.com --agree-tos --non-interactive
If successful, certificate files will be in ./certs/immai.acintia.com/
and will be mounted into the nginx container.
-
Reload nginx to pick up the certificates:
docker compose restart nginx
-
(Optional) Set up automatic renewal (cron on host) using certbot renew and reload nginx after renewal.
Local dev alternative (self-signed)
If you don't want to use certbot yet, you can create a self-signed cert and mount it into ./certs
with the same filenames fullchain.pem
and privkey.pem
for testing.
Additional operational notes
-
If you use Cloudflare, you may prefer to enable Cloudflare proxy and use their TLS — in that case you'd point nginx to use Cloudflare origin certs or use
ssl_certificate
accordingly. -
Make sure your
backend
FastAPI app binds to0.0.0.0
(the provided Dockerfile uses that). If your FastAPIuvicorn.run
call uses127.0.0.1
, update it. -
In production, consider using environment variables or a
.env
file for secrets and configuration. Also increase uvicorn worker count appropriately. -
Monitor logs:
docker compose logs -f nginx
anddocker compose logs -f backend
.
Troubleshooting
-
Certbot fails: ensure port 80 isn't blocked and DNS resolves. Run certbot with
--staging
to test. -
Backend 502 from nginx: check the
proxy_pass
host/port match your compose service names and ports; usedocker compose ps
to verify.
If you want, I can also:
-
Provide a
.env
and systemd unit file for auto-start on server boot. -
Add a healthcheck to the backend service and change nginx config to use
proxy_pass http://backend:8000;
more generally.
End of file.
No comments:
Post a Comment