Fix production deployment: replace serve with nginx reverse proxy

Frontend container now uses nginx to serve static files and proxy
/api/* requests to the backend container internally, eliminating
the hardcoded localhost:8000 build-time URL that caused "Network
error" on any non-local server. CORS origins are also configurable
via ALLOWED_ORIGINS env var.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 20:18:38 +02:00
parent a2ca82062e
commit 672f5b74a4
4 changed files with 39 additions and 14 deletions
+9 -1
View File
@@ -1,3 +1,5 @@
import os
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text from sqlalchemy import text
@@ -9,9 +11,15 @@ Base.metadata.create_all(bind=engine)
app = FastAPI(title="BTC Portfolio API") app = FastAPI(title="BTC Portfolio API")
_raw_origins = os.environ.get(
"ALLOWED_ORIGINS",
"http://localhost:3000,http://localhost:3001",
)
allowed_origins = [o.strip() for o in _raw_origins.split(",") if o.strip()]
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["http://localhost:3000", "http://localhost:3001"], allow_origins=allowed_origins,
allow_credentials=True, allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization"], allow_headers=["Content-Type", "Authorization"],
+4 -3
View File
@@ -8,6 +8,7 @@ services:
environment: environment:
- DATABASE_URL=sqlite:////app/data/btc_portfolio.db - DATABASE_URL=sqlite:////app/data/btc_portfolio.db
- SECRET_KEY=${SECRET_KEY:-dev-insecure-key-change-me} - SECRET_KEY=${SECRET_KEY:-dev-insecure-key-change-me}
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-http://localhost:3000,http://localhost:3001}
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/')"] test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/')"]
@@ -20,14 +21,14 @@ services:
build: build:
context: ./frontend context: ./frontend
args: args:
- REACT_APP_API_URL=http://localhost:8000 - REACT_APP_API_URL=/api
ports: ports:
- "3001:3001" - "3001:80"
depends_on: depends_on:
- backend - backend
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/"] test: ["CMD", "wget", "-qO-", "http://localhost:80/"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
+6 -10
View File
@@ -4,16 +4,12 @@ WORKDIR /app
COPY package.json ./ COPY package.json ./
RUN npm install RUN npm install
COPY . . COPY . .
ARG REACT_APP_API_URL=http://localhost:8000 ARG REACT_APP_API_URL=/api
ENV REACT_APP_API_URL=$REACT_APP_API_URL ENV REACT_APP_API_URL=$REACT_APP_API_URL
RUN npm run build RUN npm run build
FROM node:18-alpine FROM nginx:alpine
RUN npm install -g serve COPY --from=build /app/build /usr/share/nginx/html
RUN addgroup -S appgroup && adduser -S appuser -G appgroup COPY nginx.conf /etc/nginx/conf.d/default.conf
WORKDIR /app EXPOSE 80
COPY --from=build /app/build ./build CMD ["nginx", "-g", "daemon off;"]
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 3001
CMD ["serve", "-s", "build", "-l", "3001"]
+20
View File
@@ -0,0 +1,20 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location /api/ {
proxy_pass http://backend:8000/;
proxy_http_version 1.1;
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 / {
try_files $uri $uri/ /index.html;
}
}