diff --git a/docker-compose.yml b/docker-compose.yml index c6468be..ccec156 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,17 @@ services: + # نقطه ورود واحد — مسیریابی / (سایت استاتیک)، /api (بک‌اند) و /media (MinIO) + traefik: + image: traefik:v3 + restart: unless-stopped + command: + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + ports: + - "5173:80" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + db: image: postgres:17-alpine restart: unless-stopped @@ -14,6 +27,51 @@ services: timeout: 5s retries: 12 + minio: + image: minio/minio:latest + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: aramesh + MINIO_ROOT_PASSWORD: aramesh-minio-secret + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 5s + timeout: 5s + retries: 12 + ports: + - "9001:9001" # کنسول MinIO + labels: + - traefik.enable=true + - traefik.http.routers.media.rule=PathPrefix(`/media`) + - traefik.http.routers.media.entrypoints=web + - traefik.http.routers.media.priority=100 + - traefik.http.routers.media.middlewares=media-rewrite + - traefik.http.middlewares.media-rewrite.replacepathregex.regex=^/media/(.*) + - traefik.http.middlewares.media-rewrite.replacepathregex.replacement=/aramesh-media/$$1 + - traefik.http.services.media.loadbalancer.server.port=9000 + + # یک‌بار اجرا می‌شود: ساخت باکت و بازگذاشتن خواندن عمومی برای سرو تصاویر + minio-setup: + image: minio/mc:latest + depends_on: + minio: + condition: service_healthy + environment: + MINIO_ROOT_USER: aramesh + MINIO_ROOT_PASSWORD: aramesh-minio-secret + MINIO_BUCKET: aramesh-media + restart: "no" + entrypoint: > + /bin/sh -c " + mc alias set local http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD && + mc mb --ignore-existing local/$$MINIO_BUCKET && + mc anonymous set download local/$$MINIO_BUCKET && + echo 'minio-setup: bucket ready' + " + backend: build: ./backend restart: unless-stopped @@ -23,12 +81,24 @@ services: CORS_ORIGINS: '["http://localhost:5173","http://127.0.0.1:5173"]' ADMIN_USERNAME: admin ADMIN_PASSWORD: aramesh123 + S3_ENDPOINT: http://minio:9000 + S3_ACCESS_KEY: aramesh + S3_SECRET_KEY: aramesh-minio-secret + S3_BUCKET: aramesh-media + MEDIA_PUBLIC_BASE: /media depends_on: db: condition: service_healthy - ports: - - "8000:8000" + minio: + condition: service_healthy + labels: + - traefik.enable=true + - traefik.http.routers.api.rule=PathPrefix(`/api`) + - traefik.http.routers.api.entrypoints=web + - traefik.http.routers.api.priority=100 + - traefik.http.services.api.loadbalancer.server.port=8000 + # فقط سرو فایل‌های استاتیک SPA — همه مسیریابی‌ها روی Traefik است frontend: build: context: ./frontend @@ -37,8 +107,14 @@ services: restart: unless-stopped depends_on: - backend - ports: - - "5173:80" + - minio + labels: + - traefik.enable=true + - traefik.http.routers.web.rule=PathPrefix(`/`) + - traefik.http.routers.web.entrypoints=web + - traefik.http.routers.web.priority=1 + - traefik.http.services.web.loadbalancer.server.port=80 volumes: db_data: + minio_data: diff --git a/frontend/Caddyfile b/frontend/Caddyfile new file mode 100644 index 0000000..71db35c --- /dev/null +++ b/frontend/Caddyfile @@ -0,0 +1,12 @@ +:80 { + root * /srv + encode gzip + + # مسیریابی SPA — مسیرهای ناشناخته به index.html برمی‌گردند + try_files {path} /index.html + file_server + + # کش بلندمدت برای فایل‌های هش‌دار بیلد + @assets path /assets/* + header @assets Cache-Control "public, immutable, max-age=31536000" +} diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 846d933..38973e8 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -8,16 +8,17 @@ RUN npm ci COPY . . -# آدرس API در زمان بیلد؛ پیش‌فرض خالی یعنی درخواست‌های نسبی (/api) که nginx آن‌ها را به بک‌اند پروکسی می‌کند +# آدرس API در زمان بیلد؛ پیش‌فرض خالی یعنی درخواست‌های نسبی (/api) که Traefik مسیریابی می‌کند ARG VITE_API_URL="" ENV VITE_API_URL=$VITE_API_URL RUN npm run build # ---------- مرحله سرو ---------- -FROM nginx:alpine +# Caddy فقط فایل‌های استاتیک SPA را سرو می‌کند؛ مسیریابی /api و /media روی Traefik است +FROM caddy:2-alpine -COPY --from=build /app/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY Caddyfile /etc/caddy/Caddyfile +COPY --from=build /app/dist /srv EXPOSE 80 diff --git a/frontend/nginx.conf b/frontend/nginx.conf deleted file mode 100644 index 6064268..0000000 --- a/frontend/nginx.conf +++ /dev/null @@ -1,28 +0,0 @@ -server { - listen 80; - server_name _; - - root /usr/share/nginx/html; - index index.html; - - # مسیریابی SPA — همه مسیرها به index.html برمی‌گردند - location / { - try_files $uri $uri/ /index.html; - } - - # پروکسی درخواست‌های API به سرویس بک‌اند - location /api/ { - proxy_pass http://backend:8000; - 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 ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?)$ { - expires 30d; - add_header Cache-Control "public, immutable"; - try_files $uri =404; - } -} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 5a33944..b995e05 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -4,4 +4,11 @@ import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], + server: { + // در حالت توسعه، /api و /media به Traefik پروکسی می‌شوند (پورت ۵۱۷۳ روی هاست) + proxy: { + '/api': 'http://localhost:5173', + '/media': 'http://localhost:5173', + }, + }, })