diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..1d50b0f6 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,10 @@ +node_modules +dist +e2e +playwright-report +test-results +*.tsbuildinfo +.env +.env.* +.DS_Store +npm-debug.log* \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..ebacc1b8 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,41 @@ +# Multi-stage build: Node build stage + nginx runtime stage +# Docs: https://vite.dev/guide/static-deploy + +# ---------- Build stage ---------- +FROM node:20-alpine AS builder + +WORKDIR /app + +# Install dependencies first for better layer caching +COPY package.json package-lock.json ./ +RUN npm ci + +# Copy source and build +COPY . . + +# Build the static site (tsc + vite build) +ARG VITE_API_URL +ARG VITE_STELLAR_NETWORK +ARG VITE_STELLAR_NETWORK_PASSPHRASE +ARG VITE_STELLAR_HORIZON_URL +ENV VITE_API_URL=${VITE_API_URL} \ + VITE_STELLAR_NETWORK=${VITE_STELLAR_NETWORK} \ + VITE_STELLAR_NETWORK_PASSPHRASE=${VITE_STELLAR_NETWORK_PASSPHRASE} \ + VITE_STELLAR_HORIZON_URL=${VITE_STELLAR_HORIZON_URL} + +RUN npm run build + +# ---------- Runtime stage ---------- +FROM nginx:1.27-alpine AS production + +# SPA routing with fallback to index.html + caching headers +COPY nginx.conf /etc/nginx/conf.d/default.conf + +COPY --from=builder /app/dist /usr/share/nginx/html + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget -qO- http://localhost/ > /dev/null || exit 1 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 00000000..78643b41 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,29 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Gzip static assets + gzip on; + gzip_types text/plain text/css application/javascript application/json image/svg+xml; + gzip_min_length 1024; + + # Hashed assets (js/css with content hash) — cache forever + location ~* \.(?:js|css|png|jpg|jpeg|gif|webp|avif|svg|ico|woff2?)$ { + expires 1y; + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # index.html — never cache so deploys propagate immediately + location = /index.html { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } + + # SPA routing — fall back to index.html for client-side routes + location / { + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file