fix: fix bug docker

This commit is contained in:
2026-09-02 20:29:41 +03:30
parent c5bfa5544c
commit aeefe075dd
9 changed files with 118 additions and 65 deletions

View File

@@ -18,6 +18,8 @@ app
.git .git
.gitignore .gitignore
.github .github
.agents
.codex
# Docker # Docker
Dockerfile Dockerfile
@@ -27,7 +29,6 @@ docker-compose*.yml
# Env / secrets # Env / secrets
.env .env
.env.* .env.*
!.env.production
build.sh build.sh
# Editor / OS # Editor / OS

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
APP_PORT=8080
VITE_API_URL=https://api.persain-game.com/api/v1
PERSIAN_GAME_IMAGE=persian-game:local

3
.gitignore vendored
View File

@@ -11,6 +11,9 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
.env
.env.*
!.env.example
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

View File

@@ -1,36 +1,31 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1.7
# ---------- Build stage ---------- FROM node:22-alpine AS build
FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
# Install dependencies (cached unless package files change)
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci RUN --mount=type=cache,target=/root/.npm \
npm ci --no-audit --no-fund
# Build the app
COPY . . COPY . .
# API base URL is baked in at build time (Vite inlines VITE_* vars). # Vite embeds VITE_* variables in the static bundle during this stage.
# Override with: docker build --build-arg VITE_API_URL=https://api.example.com/api/v1 . ARG VITE_API_URL=https://api.persain-game.com/api/v1
ARG VITE_API_URL ENV VITE_API_URL=${VITE_API_URL}
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build RUN npm run build
# ---------- Runtime stage ---------- FROM nginxinc/nginx-unprivileged:1.28-alpine AS runtime
FROM nginx:alpine AS runtime
# SPA-aware nginx config COPY --chown=nginx:nginx nginx.conf /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build --chown=nginx:nginx /app/dist /usr/share/nginx/html
# Static build output USER nginx
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80 EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -q -O /dev/null http://127.0.0.1/ || exit 1 CMD wget -q -O /dev/null http://127.0.0.1:8080/healthz || exit 1
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,30 +1,60 @@
# React + TypeScript + Vite # Persian Game frontend
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. رابط کاربری React + TypeScript این پروژه با Vite ساخته و در محیط production توسط Nginx سرو می‌شود.
Currently, two official plugins are available: ## اجرای Docker
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh پیش‌نیازها: Docker Engine و Docker Compose v2.
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration ```bash
cp .env.example .env
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: docker compose up --detach --build
- Configure the top-level `parserOptions` property like this:
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
``` ```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` سپس برنامه روی `http://localhost:8080` در دسترس است. برای مشاهده وضعیت و لاگ‌ها:
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list ```bash
docker compose ps
docker compose logs --follow web
```
برای توقف برنامه:
```bash
docker compose down
```
اسکریپت زیر نیز Compose را اعتبارسنجی، image را build و سرویس را اجرا می‌کند:
```bash
./build.sh
```
## تنظیمات
مقادیر قابل تنظیم در `.env`:
- `APP_PORT`: پورت میزبان؛ مقدار پیش‌فرض `8080` است.
- `VITE_API_URL`: آدرس کامل API؛ مقدار پیش‌فرض `https://api.persain-game.com/api/v1` است.
- `PERSIAN_GAME_IMAGE`: نام و tag ایمیج ساخته‌شده.
متغیر `VITE_API_URL` هنگام build داخل فایل‌های استاتیک قرار می‌گیرد. پس از تغییر آن باید image را دوباره build کنید:
```bash
docker compose build --no-cache web
docker compose up --detach web
```
## توسعه محلی بدون Docker
```bash
npm ci
npm run dev
```
بررسی production build و lint:
```bash
npm run build
npm run lint
```

17
build.sh Normal file → Executable file
View File

@@ -1,13 +1,10 @@
#!/bin/bash #!/usr/bin/env sh
REPO_DIR="/var/www/html/persian-game/" set -eu
TOKEN="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" SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$SCRIPT_DIR"
cd $REPO_DIR docker compose config --quiet
docker compose up --detach --build --remove-orphans
echo $TOKEN | git pull https://$TOKEN@github.com/joshfe/persian-game.git docker compose ps
npm i
npm run build
ls -al

19
compose.yaml Normal file
View File

@@ -0,0 +1,19 @@
services:
web:
image: ${PERSIAN_GAME_IMAGE:-persian-game:local}
build:
context: .
target: runtime
args:
VITE_API_URL: ${VITE_API_URL:-https://api.persain-game.com/api/v1}
ports:
- "${APP_PORT:-8080}:8080"
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
stop_grace_period: 10s

View File

@@ -1,10 +1,16 @@
server { server {
listen 80; listen 8080;
server_name _; server_name _;
server_tokens off;
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# ---- gzip ---- # ---- gzip ----
gzip on; gzip on;
gzip_vary on; gzip_vary on;
@@ -26,33 +32,32 @@ server {
font/woff2 font/woff2
application/vnd.ms-fontobject; application/vnd.ms-fontobject;
# ---- Hashed static assets: cache hard ---- location = /healthz {
# ^~ so this wins over the regex block below for Vite's fingerprinted files access_log off;
default_type text/plain;
return 200 "ok\n";
}
# Vite fingerprints files under /assets, so they can be cached for a year.
location ^~ /assets/ { location ^~ /assets/ {
expires 1y; expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404; try_files $uri =404;
} }
# ---- Other static files by extension ----
location ~* \.(?:js|css|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm)$ { location ~* \.(?:js|css|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm)$ {
expires 30d; expires 30d;
add_header Cache-Control "public";
try_files $uri =404; try_files $uri =404;
} }
# ---- SPA fallback: unknown routes -> index.html (React Router) ----
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
# index.html must never be cached, so new deploys are picked up # Always revalidate the entrypoint so deployments are picked up immediately.
location = /index.html { location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
expires -1; expires -1;
} }
# Don't log noise / don't cache the favicon aggressively is fine as-is
location = /favicon.ico { location = /favicon.ico {
log_not_found off; log_not_found off;
access_log off; access_log off;

View File

@@ -9,7 +9,7 @@ const useAxios = () => {
const instance = axios.create({ const instance = axios.create({
baseURL: baseURL:
import.meta.env.VITE_API_URL ?? "https://api.persain-game.com/api/v1", import.meta.env.VITE_API_URL || "https://api.persain-game.com/api/v1",
maxBodyLength: Infinity, maxBodyLength: Infinity,
...(sessionStorage.getItem("shop_id") && { ...(sessionStorage.getItem("shop_id") && {
headers: { headers: {