diff --git a/PROJECTS/beginner/canary-token-generator/infra/docker/canary.dev b/PROJECTS/beginner/canary-token-generator/infra/docker/canary.dev new file mode 100644 index 00000000..38161551 --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/infra/docker/canary.dev @@ -0,0 +1,25 @@ +# ============================================================================= +# ©AngelaMos | 2026 +# canary.dev +# ============================================================================= +# Development image: golang:1.25-alpine + Air for hot reload. +# Source is bind-mounted by dev.compose.yml; Air rebuilds on file change. +# +# Build context: ./backend +# Reads .air.toml from the bind-mounted source. +# ============================================================================= + +FROM golang:1.25-alpine + +RUN apk add --no-cache git curl wget + +RUN go install github.com/air-verse/air@latest + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +EXPOSE 8080 + +CMD ["air", "-c", ".air.toml"] diff --git a/PROJECTS/beginner/canary-token-generator/infra/docker/canary.prod b/PROJECTS/beginner/canary-token-generator/infra/docker/canary.prod new file mode 100644 index 00000000..246d7db1 --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/infra/docker/canary.prod @@ -0,0 +1,41 @@ +# ============================================================================= +# ©AngelaMos | 2026 +# canary.prod +# ============================================================================= +# Multi-stage build: +# Stage 1 (builder) golang:1.25-alpine, CGO_ENABLED=0, trimpath, ldflags +# Stage 2 (final) gcr.io/distroless/static:nonroot — no shell, non-root +# +# Build context: ./backend (set by compose.yml) +# Result: /canary single static binary, runs as nonroot user. +# ============================================================================= + +FROM golang:1.25-alpine AS builder + +RUN apk add --no-cache ca-certificates git + +WORKDIR /src + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build \ + -trimpath \ + -ldflags="-s -w" \ + -o /out/canary \ + ./cmd/canary + +FROM gcr.io/distroless/static:nonroot AS final + +WORKDIR / + +COPY --from=builder /out/canary /canary +COPY --from=builder /src/config.yaml /config.yaml + +EXPOSE 8080 +USER nonroot:nonroot + +ENTRYPOINT ["/canary"] +CMD ["-config", "/config.yaml"] diff --git a/PROJECTS/beginner/canary-token-generator/infra/docker/vite.dev b/PROJECTS/beginner/canary-token-generator/infra/docker/vite.dev new file mode 100644 index 00000000..2fafe7fd --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/infra/docker/vite.dev @@ -0,0 +1,25 @@ +# ============================================================================= +# AngelaMos | 2026 +# vite.dev +# ============================================================================= +# Development Dockerfile for Vite/React frontend +# Features: pnpm, HMR support, polling for Docker file watching +# ============================================================================= +# syntax=docker/dockerfile:1 + +FROM node:22-slim + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml* ./ + +RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile + +COPY . . + +EXPOSE 5173 + +CMD ["pnpm", "dev", "--host", "0.0.0.0"] diff --git a/PROJECTS/beginner/canary-token-generator/infra/docker/vite.prod b/PROJECTS/beginner/canary-token-generator/infra/docker/vite.prod new file mode 100644 index 00000000..48bbf366 --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/infra/docker/vite.prod @@ -0,0 +1,59 @@ +# ============================================================================= +# AngelaMos | 2026 +# vite.prod +# ============================================================================= +# Production Dockerfile: builds Vite app, serves via Nginx +# ============================================================================= +# syntax=docker/dockerfile:1 + +# ============================================================================ +# BUILD STAGE +# ============================================================================ +FROM node:22-slim AS builder + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY react-scss/package.json react-scss/pnpm-lock.yaml* ./ + +RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile + +COPY react-scss/ . + +ARG VITE_API_URL=/api +ARG VITE_APP_TITLE="My App" + +ENV VITE_API_URL=${VITE_API_URL} \ + VITE_APP_TITLE=${VITE_APP_TITLE} + +RUN pnpm build + +# ============================================================================ +# PRODUCTION STAGE +# ============================================================================ +FROM nginx:1.27-alpine AS production + +RUN rm -rf /usr/share/nginx/html/* && \ + rm /etc/nginx/conf.d/default.conf + +COPY --from=builder /app/dist /usr/share/nginx/html + +COPY --chown=nginx:nginx infra/nginx/nginx.prod.conf /etc/nginx/nginx.conf +COPY --chown=nginx:nginx infra/nginx/prod.nginx /etc/nginx/conf.d/default.conf + +RUN chown -R nginx:nginx /usr/share/nginx/html && \ + chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + touch /var/run/nginx.pid && \ + chown -R nginx:nginx /var/run/nginx.pid + +USER nginx + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:80/health || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/PROJECTS/beginner/canary-token-generator/infra/nginx/dev.nginx b/PROJECTS/beginner/canary-token-generator/infra/nginx/dev.nginx new file mode 100644 index 00000000..7ea1f6ec --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/infra/nginx/dev.nginx @@ -0,0 +1,35 @@ +# ============================================================================= +# AngelaMos | 2026 +# dev.nginx +# ============================================================================= +# Development server block: proxies to Vite dev server with HMR + +server { + listen 80; + listen [::]:80; + server_name _; + + access_log /var/log/nginx/access.log main_timed; + error_log /var/log/nginx/error.log debug; + + add_header Cache-Control "no-store, no-cache, must-revalidate" always; + + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + location / { + proxy_pass http://frontend_dev; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + 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_read_timeout 60s; + proxy_buffering off; + } +} diff --git a/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.conf b/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.conf new file mode 100644 index 00000000..eddade54 --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.conf @@ -0,0 +1,76 @@ +# AngelaMos | 2026 +# nginx.conf +# Development nginx configuration + +user nginx; +worker_processes auto; +worker_rlimit_nofile 65535; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 4096; + multi_accept on; + use epoll; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + upstream frontend_dev { + server frontend:5173; + keepalive 8; + } + + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; + limit_conn_zone $binary_remote_addr zone=conn_limit:10m; + limit_req_status 429; + + log_format main_timed '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time"'; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + client_body_buffer_size 128k; + client_header_buffer_size 16k; + client_max_body_size 10m; + large_client_header_buffers 4 16k; + + client_body_timeout 12s; + client_header_timeout 12s; + send_timeout 10s; + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_min_length 256; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + gzip_disable "msie6"; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.prod.conf b/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.prod.conf new file mode 100644 index 00000000..f73ad69b --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/infra/nginx/nginx.prod.conf @@ -0,0 +1,65 @@ +# AngelaMos | 2026 +# nginx.prod.conf +# Production nginx configuration + +worker_processes auto; +worker_rlimit_nofile 65535; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 4096; + multi_accept on; + use epoll; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; + limit_conn_zone $binary_remote_addr zone=conn_limit:10m; + limit_req_status 429; + + log_format main_timed '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt=$request_time uct="$upstream_connect_time" ' + 'uht="$upstream_header_time" urt="$upstream_response_time"'; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + client_body_buffer_size 128k; + client_header_buffer_size 16k; + client_max_body_size 10m; + large_client_header_buffers 4 16k; + + client_body_timeout 12s; + client_header_timeout 12s; + send_timeout 10s; + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_min_length 256; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + gzip_disable "msie6"; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/PROJECTS/beginner/canary-token-generator/infra/nginx/prod.nginx b/PROJECTS/beginner/canary-token-generator/infra/nginx/prod.nginx new file mode 100644 index 00000000..f6a747ef --- /dev/null +++ b/PROJECTS/beginner/canary-token-generator/infra/nginx/prod.nginx @@ -0,0 +1,54 @@ +# ============================================================================= +# AngelaMos | 2026 +# prod.nginx +# ============================================================================= +# Production server block: serves built static files +# Note: SSL handled by Cloudflare, not here + +server { + listen 80; + listen [::]:80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + access_log /var/log/nginx/access.log main_timed buffer=32k flush=5s; + error_log /var/log/nginx/error.log warn; + + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; + + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + try_files $uri =404; + } + + location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|avif|woff|woff2|ttf|eot|otf)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + location / { + add_header Cache-Control "no-cache, must-revalidate"; + try_files $uri $uri/ /index.html; + } + + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } +}