From b19520eb6efbfb468b4524ce6882a0bf47ef2006 Mon Sep 17 00:00:00 2001 From: ae Date: Tue, 22 Apr 2025 19:38:31 +0300 Subject: [PATCH] feat: fullstack dockerization --- .env.template | 1 - docker-compose-dev-backend.yml | 2 +- docker-compose.yml | 15 ++++++++---- scripts/run_full_dev.sh | 10 ++++++++ web/Dockerfile | 19 +++++++++++++++ web/nginx.conf | 41 ++++++++++++++++++++++++++++---- web/package-lock.json | 11 +++++++++ web/package.json | 1 + web/src/lib/const.ts | 5 ++-- web/src/routes/+layout.server.ts | 2 ++ web/svelte.config.js | 25 ++++++++++--------- web/vite.config.ts | 2 +- 12 files changed, 109 insertions(+), 25 deletions(-) create mode 100755 scripts/run_full_dev.sh create mode 100644 web/Dockerfile create mode 100644 web/src/routes/+layout.server.ts diff --git a/.env.template b/.env.template index 842943a..72250b1 100644 --- a/.env.template +++ b/.env.template @@ -17,5 +17,4 @@ FRONTEND_URL="" # Frontend VITE_VIEW_COOKIE_PATH="/" -VITE_VIEW_COOKIE_DOMAIN=$DOMAIN VITE_COKOIE_SAME_SITE="strict" diff --git a/docker-compose-dev-backend.yml b/docker-compose-dev-backend.yml index 1b0f8aa..4caffd8 100644 --- a/docker-compose-dev-backend.yml +++ b/docker-compose-dev-backend.yml @@ -40,7 +40,7 @@ services: LOG_LEVEL: debug APP_ENV: development DOMAIN: localhost - FRONTEND_URL: http://localhost + FRONTEND_URL: http://localhost:5173 networks: notatest: diff --git a/docker-compose.yml b/docker-compose.yml index f516117..57cfc67 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,18 +38,25 @@ services: LOG_LEVEL: ${LOG_LEVEL:-info} APP_ENV: ${APP_ENV:-production} DOMAIN: ${DOMAIN:-localhost} - FRONTEND_URL: ${FRONTEND_URL:-http://localhost:5173} + FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000} notatest-web: - build: ${PWD}/web - image: notatest/web container_name: notatest-web + build: + context: ${PWD}/web + dockerfile: ${PWD}/web/Dockerfile + image: notatest-web:latest networks: - notatest + # Add potential reverse proxy's local network here ports: - - 3000:80 # Defined in nginx.conf + - 3000:80 # Container port defined in nginx.conf (bound to 3000 for dev.) depends_on: - notatest-server + environment: + VITE_VIEW_COOKIE_PATH: ${VITE_VIEW_COOKIE_PATH:-/} + VITE_VIEW_COOKIE_DOMAIN: ${DOMAIN:-localhost} + VITE_COOKIE_SAME_SITE: ${VITE_COOKIE_SAME_SITE:-strict} networks: notatest: diff --git a/scripts/run_full_dev.sh b/scripts/run_full_dev.sh new file mode 100755 index 0000000..9697275 --- /dev/null +++ b/scripts/run_full_dev.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +DEV_COMPOSE_FILE="docker-compose.yml" + +[ "$( docker container inspect -f '{{.State.Status}}' notatest-psql )" = "running" ] || docker compose up notatest-psql -d + +# Shutdown, rebuild, & restart +docker compose stop notatest-server notatest-web && docker compose rm -f notatest-server notatest-web +docker compose -f $DEV_COMPOSE_FILE build +docker compose -f $DEV_COMPOSE_FILE up notatest-server notatest-web diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..4e2f157 --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,19 @@ +# Build stage +FROM node:23.11-alpine3.20 AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# Final stage (optimized image size + NGINX) +FROM nginx:alpine + +WORKDIR /app +COPY --from=builder /app/build /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/web/nginx.conf b/web/nginx.conf index 8d82099..a5e0f56 100644 --- a/web/nginx.conf +++ b/web/nginx.conf @@ -1,15 +1,48 @@ server { - listen 80; # TLS termination is handled by Traefik + listen 80; + root /usr/share/nginx/html; - location / { - root /usr/share/nginx/html; - try_files $uri $uri/ /index.html; + # TLS termination should be handled by a reverse proxy (e.g. Traefik) + + # General security headers + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; + + # Block access to dotfiles + location ~ /\.(?!well-known) { + deny all; + access_log off; + log_not_found off; } + # Enable gzip compression for all assets + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + location /api { proxy_pass http://notatest-server:8080; # Internal Docker DNS 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 / { + try_files $uri $uri/ /index.html; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 7d; + add_header Cache-Control "public, no-transform"; + } + } + + # Route 404 errors to front page + error_page 404 /; } \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index b6122a0..7f8c458 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@sveltejs/adapter-auto": "^4.0.0", + "@sveltejs/adapter-static": "^3.0.8", "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/forms": "^0.5.9", @@ -830,6 +831,16 @@ "@sveltejs/kit": "^2.0.0" } }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", + "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, "node_modules/@sveltejs/kit": { "version": "2.20.7", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.7.tgz", diff --git a/web/package.json b/web/package.json index 4e07942..cd1dd67 100644 --- a/web/package.json +++ b/web/package.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@sveltejs/adapter-auto": "^4.0.0", + "@sveltejs/adapter-static": "^3.0.8", "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/forms": "^0.5.9", diff --git a/web/src/lib/const.ts b/web/src/lib/const.ts index 3c5f246..4d477aa 100644 --- a/web/src/lib/const.ts +++ b/web/src/lib/const.ts @@ -1,6 +1,5 @@ -// TODO: this can be set to always be "/api" after Dockerization as the backend requests -// will automatically be proxied to the correct destination -export const API_BASE_ADDR = import.meta.env.PROD ? "/api" : "http://localhost:8080/api" +// export const API_BASE_ADDR = import.meta.env.PROD ? "/api" : "http://localhost:8080/api" +export const API_BASE_ADDR = "/api" // Lifetimes of *in-memory* authentication tokens in milliseconds export const AT_EXP_MS = 15 * 60 * 1000 // 15 min. diff --git a/web/src/routes/+layout.server.ts b/web/src/routes/+layout.server.ts new file mode 100644 index 0000000..27adb2b --- /dev/null +++ b/web/src/routes/+layout.server.ts @@ -0,0 +1,2 @@ +export const ssr = false +export const prerender = false diff --git a/web/svelte.config.js b/web/svelte.config.js index 1295460..f31aa6f 100644 --- a/web/svelte.config.js +++ b/web/svelte.config.js @@ -1,18 +1,21 @@ -import adapter from '@sveltejs/adapter-auto'; -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +import adapter from "@sveltejs/adapter-static" +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte" /** @type {import('@sveltejs/kit').Config} */ const config = { - // Consult https://svelte.dev/docs/kit/integrations - // for more information about preprocessors preprocess: vitePreprocess(), - kit: { - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter() + adapter: adapter({ + pages: "build", + assets: "build", + fallback: "index.html", + precompress: false + }) + }, + ssr: false, + prerender: { + entries: [] } -}; +} -export default config; +export default config diff --git a/web/vite.config.ts b/web/vite.config.ts index 73f8d9d..4f42393 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,5 +1,5 @@ -import tailwindcss from "@tailwindcss/vite" import { sveltekit } from "@sveltejs/kit/vite" +import tailwindcss from "@tailwindcss/vite" import { defineConfig } from "vite" export default defineConfig({