From 984b1a68a6447ae8db67fd1b01337b37ebd36ac5 Mon Sep 17 00:00:00 2001 From: WillowMT Date: Fri, 20 Feb 2026 14:03:24 +0700 Subject: [PATCH] added docker support --- DOKPLOY.md | 46 ++++++++++++++++++++++++++++++++++++++++++ backend/.dockerignore | 10 +++++++++ backend/Dockerfile | 27 +++++++++++++++++++++++++ backend/main.py | 4 +++- docker-compose.yml | 30 +++++++++++++++++++++++++++ frontend/.dockerignore | 7 +++++++ frontend/Dockerfile | 20 ++++++++++++++++++ frontend/nginx.conf | 23 +++++++++++++++++++++ 8 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 DOKPLOY.md create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 docker-compose.yml create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile create mode 100644 frontend/nginx.conf diff --git a/DOKPLOY.md b/DOKPLOY.md new file mode 100644 index 0000000..55b2475 --- /dev/null +++ b/DOKPLOY.md @@ -0,0 +1,46 @@ +# Dokploy Deployment + +## Assumptions + +- **Frontend**: React Vite SPA built with Bun, served by nginx. Single public entry point. +- **Backend**: FastAPI, internal only. No Traefik labels, no public ports. +- **Routing**: nginx proxies `/api` to backend. Traefik routes all traffic to frontend (port 80). +- **Domain**: Replace `your-domain.com` in Traefik labels with your actual domain. + +## Build & Run Locally + +```bash +# Create network (Dokploy provides this) +docker network create dokploy-network 2>/dev/null || true + +# Build and run +docker compose up --build +``` + +Frontend will be on port 80 (or the mapped port). Backend is internal. + +## Dokploy Setup + +1. Create `dokploy-network` if not exists: + ```bash + docker network create dokploy-network + ``` + +2. Deploy via Dokploy UI: add project, use this `docker-compose.yml`. + +3. Set environment variables in Dokploy: + - `CORS_ORIGINS`: Comma-separated allowed origins (e.g. `https://your-domain.com`) + +4. Update Traefik labels in `docker-compose.yml`: + - Replace `your-domain.com` with your domain + - Ensure `certResolver=letsencrypt` matches your Traefik config + +## Port Rules + +- **Frontend**: Exposes port 80 internally. Traefik routes to it. +- **Backend**: No ports exposed. Communicates via `dokploy-network`. + +## Service Communication + +- Frontend (nginx) proxies `/api` to `http://backend:8000` +- Backend is reachable only from within the Docker network diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..510ef4e --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,10 @@ +.venv +__pycache__ +*.pyc +*.pyo +.pytest_cache +.coverage +htmlcov +.env +.env.* +*.md diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..f0da440 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,27 @@ +# syntax=docker/dockerfile:1 +FROM python:3.12-alpine AS builder + +WORKDIR /app + +RUN pip install --no-cache-dir --upgrade pip + +COPY requirements.txt . +RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt + +# --- +FROM python:3.12-alpine AS runtime + +RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001 -G appgroup + +WORKDIR /app + +COPY --from=builder /wheels /wheels +RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels + +COPY --chown=appuser:appgroup main.py . + +USER appuser + +EXPOSE 8000 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/main.py b/backend/main.py index dd82379..d213d8e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,3 +1,4 @@ +import os from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -7,9 +8,10 @@ app = FastAPI( version="0.1.0", ) +_cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:5173").split(",") app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:5173"], + allow_origins=[o.strip() for o in _cors_origins if o.strip()], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c314f77 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +services: + backend: + build: + context: ./backend + dockerfile: Dockerfile + restart: always + networks: + - dokploy-network + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + restart: always + ports: + - 80 + networks: + - dokploy-network + depends_on: + - backend + labels: + - "traefik.enable=true" + - "traefik.http.routers.frontend.rule=Host(`your-domain.com`)" + - "traefik.http.routers.frontend.entrypoints=websecure" + - "traefik.http.routers.frontend.tls.certResolver=letsencrypt" + - "traefik.http.services.frontend.loadbalancer.server.port=80" + +networks: + dokploy-network: + external: true diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..68fdccb --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,7 @@ +node_modules +dist +.git +.env +.env.* +*.md +.DS_Store diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..21d12f0 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,20 @@ +# syntax=docker/dockerfile:1 +FROM oven/bun:1-alpine AS builder + +WORKDIR /app + +COPY package.json bun.lock* package-lock.json* pnpm-lock.yaml* yarn.lock* ./ +RUN bun install --frozen-lockfile || bun install + +COPY . . +RUN bun run build + +# --- +FROM nginx:alpine AS runtime + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..25c4ac5 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,23 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + rewrite ^/api/?(.*) /$1 break; + 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; + } + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; +}