added caddy

This commit is contained in:
2026-06-17 11:20:36 +02:00
parent a2ccec4bb1
commit 8b1b9cedc2
7 changed files with 137 additions and 2 deletions
+7 -1
View File
@@ -11,9 +11,14 @@ BACKEND_PORT=8080
# 64+ hex chars. Generate: openssl rand -hex 32
SESSION_SECRET=please_generate_a_long_random_secret_at_least_64_chars_xxxxxxxxxx
# Public URL of the frontend, used to build verification/reset links.
# Prod: https://consume.narl.io
PUBLIC_APP_URL=http://localhost:5173
# Comma-separated allowed CORS origins.
# Prod (same-origin behind Caddy): https://consume.narl.io
CORS_ORIGINS=http://localhost:5173
# Mark the session cookie Secure (HTTPS-only). Auto-on when PUBLIC_APP_URL is
# https; override here. Must be true in production, false for plain-http dev.
# COOKIE_SECURE=true
# ── SMTP (mail notifications + verification) ────────────────
SMTP_HOST=smtp.example.com
@@ -25,5 +30,6 @@ SMTP_FROM="Shopping List <no-reply@example.com>"
SMTP_SECURITY=starttls
# ── Frontend ────────────────────────────────────────────────
# Where the SvelteKit app reaches the backend (server-side).
# Base origin the browser uses to reach the backend (no trailing /api).
# Prod (same-origin behind Caddy): https://consume.narl.io
PUBLIC_API_BASE=http://localhost:8080
+11
View File
@@ -0,0 +1,11 @@
consume.narl.io {
# Backend API (routes are served under /api by the Rust app).
handle /api/* {
reverse_proxy backend:8080
}
# Everything else → SvelteKit (adapter-node) server.
handle {
reverse_proxy frontend:3000
}
}
+22
View File
@@ -0,0 +1,22 @@
# ── Build ───────────────────────────────────────────────────
FROM rust:1-bookworm AS build
WORKDIR /app
# Cache deps: copy manifests, build a stub, then the real source.
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs \
&& cargo build --release \
&& rm -rf src
COPY . .
# Touch so cargo rebuilds with the real main.rs.
RUN touch src/main.rs && cargo build --release
# ── Runtime ─────────────────────────────────────────────────
FROM debian:bookworm-slim AS runtime
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /app/target/release/shoplist-backend /usr/local/bin/shoplist-backend
EXPOSE 8080
CMD ["shoplist-backend"]
+8
View File
@@ -16,6 +16,8 @@ pub struct Config {
pub refetch_min_age_secs: i64,
/// Default ISO 4217 currency when an adapter can't determine one.
pub default_currency: String,
/// Mark the session cookie Secure (HTTPS-only). Enable in production.
pub cookie_secure: bool,
}
#[derive(Clone, Debug)]
@@ -67,6 +69,12 @@ impl Config {
refetch_interval_secs: opt("REFETCH_INTERVAL_SECS", "300").parse()?,
refetch_min_age_secs: opt("REFETCH_MIN_AGE_SECS", "21600").parse()?,
default_currency: opt("DEFAULT_CURRENCY", "EUR").to_uppercase(),
// Default Secure when the public URL is HTTPS; override with COOKIE_SECURE.
cookie_secure: env::var("COOKIE_SECURE")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or_else(|_| {
opt("PUBLIC_APP_URL", "http://localhost:5173").starts_with("https://")
}),
smtp: SmtpConfig {
host: opt("SMTP_HOST", "localhost"),
port: opt("SMTP_PORT", "587").parse()?,
+1 -1
View File
@@ -43,7 +43,7 @@ async fn main() -> anyhow::Result<()> {
session_store.migrate().await?;
let session_layer = SessionManagerLayer::new(session_store)
.with_secure(false) // set true behind HTTPS in production
.with_secure(config.cookie_secure) // true behind HTTPS in production
.with_same_site(tower_sessions::cookie::SameSite::Lax)
.with_expiry(Expiry::OnInactivity(Duration::days(30)));
+68
View File
@@ -0,0 +1,68 @@
services:
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
# No published port: db is reachable only on the compose network.
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
backend:
build: ./backend
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
BACKEND_HOST: 0.0.0.0
BACKEND_PORT: 8080
SESSION_SECRET: ${SESSION_SECRET}
COOKIE_SECURE: "true"
PUBLIC_APP_URL: ${PUBLIC_APP_URL}
CORS_ORIGINS: ${CORS_ORIGINS}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USERNAME: ${SMTP_USERNAME}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_FROM: ${SMTP_FROM}
SMTP_SECURITY: ${SMTP_SECURITY}
# No published port: reached via caddy only.
frontend:
build: ./frontend
restart: unless-stopped
depends_on:
- backend
environment:
PUBLIC_API_BASE: ${PUBLIC_API_BASE}
# adapter-node needs the public origin for CSRF/redirects behind a proxy.
ORIGIN: ${PUBLIC_APP_URL}
# No published port: reached via caddy only.
caddy:
image: caddy:2-alpine
restart: unless-stopped
depends_on:
- frontend
- backend
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
pgdata:
caddy_data:
caddy_config:
+20
View File
@@ -0,0 +1,20 @@
# ── Build ───────────────────────────────────────────────────
FROM node:22-bookworm-slim AS build
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build && pnpm prune --prod
# ── Runtime ─────────────────────────────────────────────────
FROM node:22-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/build ./build
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
EXPOSE 3000
CMD ["node", "build"]