From 78b511e65defc60022d5ac6ea49770d7d837dfcb Mon Sep 17 00:00:00 2001 From: houseassassin Date: Tue, 12 May 2026 11:46:42 +0300 Subject: [PATCH] feat: Docker + Caddy production deployment setup --- .dockerignore | 8 ++++++++ .env.example | 15 ++++++++++++--- Caddyfile | 23 +++++++++++++++++++++++ Dockerfile | 27 +++++++++++++++++++++++++++ docker-compose.yml | 29 +++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 Caddyfile create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b2d7e0b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +node_modules +dist +.env +.env.* +!.env.example +.git +.gitignore +*.log diff --git a/.env.example b/.env.example index 7ed1b0a..78dcf2a 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,14 @@ -# Скопируйте этот файл в .env и укажите ваши данные от Telegram бота -VITE_TG_BOT_TOKEN="ваш_токен_бота_от_botfather" -VITE_TG_CHAT_ID="ваш_chat_id_куда_отправлять_сообщения" +# Supabase VITE_SUPABASE_URL=https://your-project.supabase.co VITE_SUPABASE_ANON_KEY=your-anon-key + +# Telegram bot (lead form on landing page) +VITE_TG_BOT_TOKEN=your-bot-token +VITE_TG_CHAT_ID=your-chat-id + +# Web Push / VAPID (generate with the node command in .env) +VITE_VAPID_PUBLIC_KEY=your-vapid-public-key + +# Deployment domain for Caddy (automatic HTTPS) +# Use "localhost" for local dev, set to real domain in production +DOMAIN=localhost diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..7373978 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,23 @@ +{$DOMAIN:localhost} { + root * /srv + + # Gzip compression + encode gzip + + # SPA fallback — all routes serve index.html + try_files {path} /index.html + + # Cache static assets aggressively + @static { + path *.js *.css *.png *.jpg *.svg *.ico *.woff2 *.woff + } + header @static Cache-Control "public, max-age=31536000, immutable" + + # Service worker must not be cached + @sw { + path /sw.js + } + header @sw Cache-Control "no-cache, no-store, must-revalidate" + + file_server +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3a43f2d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Stage 1: build +FROM node:20-alpine AS builder +WORKDIR /app + +COPY package*.json ./ +RUN npm ci --prefer-offline + +COPY . . + +ARG VITE_SUPABASE_URL +ARG VITE_SUPABASE_ANON_KEY +ARG VITE_TG_BOT_TOKEN +ARG VITE_TG_CHAT_ID +ARG VITE_VAPID_PUBLIC_KEY + +ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL +ENV VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY +ENV VITE_TG_BOT_TOKEN=$VITE_TG_BOT_TOKEN +ENV VITE_TG_CHAT_ID=$VITE_TG_CHAT_ID +ENV VITE_VAPID_PUBLIC_KEY=$VITE_VAPID_PUBLIC_KEY + +RUN npm run build + +# Stage 2: serve with Caddy +FROM caddy:2-alpine +COPY --from=builder /app/dist /srv +COPY Caddyfile /etc/caddy/Caddyfile diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7381dce --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +services: + web: + build: + context: . + args: + VITE_SUPABASE_URL: ${VITE_SUPABASE_URL} + VITE_SUPABASE_ANON_KEY: ${VITE_SUPABASE_ANON_KEY} + VITE_TG_BOT_TOKEN: ${VITE_TG_BOT_TOKEN} + VITE_TG_CHAT_ID: ${VITE_TG_CHAT_ID} + VITE_VAPID_PUBLIC_KEY: ${VITE_VAPID_PUBLIC_KEY} + environment: + DOMAIN: ${DOMAIN:-localhost} + ports: + - "80:80" + - "443:443" + - "443:443/udp" # HTTP/3 + volumes: + - caddy_data:/data # TLS certificates (persisted across restarts) + - caddy_config:/config # Caddy config cache + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:80"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + caddy_data: + caddy_config: