docs(06): research phase domain
Phase 6: Deployment - Standard stack identified (adapter-node, node:22-alpine) - Multi-stage Dockerfile patterns documented - Native module (better-sqlite3, sharp) considerations - SQLite WAL mode and Docker volume best practices - Environment variable configuration patterns - Health check and backup script examples
This commit is contained in:
494
.planning/phases/06-deployment/06-RESEARCH.md
Normal file
494
.planning/phases/06-deployment/06-RESEARCH.md
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
# Phase 6: Deployment - Research
|
||||||
|
|
||||||
|
**Researched:** 2026-02-01
|
||||||
|
**Domain:** Docker containerization, SvelteKit production deployment
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This phase containerizes the TaskPlaner SvelteKit application for production deployment with Docker. The research confirms a well-established pattern for deploying SvelteKit with adapter-node: multi-stage Alpine builds, runtime environment variables, health checks via API routes, and named volumes for data persistence.
|
||||||
|
|
||||||
|
The key technical considerations are:
|
||||||
|
1. Switching from adapter-auto to adapter-node for production Node.js server
|
||||||
|
2. Native module compilation (better-sqlite3, sharp) requiring npm install inside the container
|
||||||
|
3. SQLite WAL mode working correctly with Docker named volumes (single host, same filesystem)
|
||||||
|
4. Environment variable configuration via adapter-node's built-in support
|
||||||
|
|
||||||
|
**Primary recommendation:** Use a two-stage Dockerfile with node:22-alpine, run npm install in the builder stage to compile native modules correctly, and configure all runtime behavior via environment variables with sensible defaults.
|
||||||
|
|
||||||
|
## Standard Stack
|
||||||
|
|
||||||
|
The established libraries/tools for this domain:
|
||||||
|
|
||||||
|
### Core
|
||||||
|
| Library | Version | Purpose | Why Standard |
|
||||||
|
|---------|---------|---------|--------------|
|
||||||
|
| @sveltejs/adapter-node | ^5.x | SvelteKit Node.js server | Official adapter, supports all env vars natively |
|
||||||
|
| node:22-alpine | LTS | Docker base image | Small (~40MB vs 350MB+), includes 'node' user |
|
||||||
|
|
||||||
|
### Supporting
|
||||||
|
| Library | Version | Purpose | When to Use |
|
||||||
|
|---------|---------|---------|-------------|
|
||||||
|
| dumb-init | ^1.2.5 | PID 1 init process | Optional: proper signal handling in containers |
|
||||||
|
|
||||||
|
### Alternatives Considered
|
||||||
|
| Instead of | Could Use | Tradeoff |
|
||||||
|
|------------|-----------|----------|
|
||||||
|
| node:alpine | node:slim (debian) | Debian avoids musl issues, but 3x larger image |
|
||||||
|
| Built-in signals | tini/dumb-init | adapter-node handles SIGTERM natively, may not need |
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
```bash
|
||||||
|
npm install -D @sveltejs/adapter-node
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Recommended Project Structure
|
||||||
|
```
|
||||||
|
project-root/
|
||||||
|
├── Dockerfile # Multi-stage build
|
||||||
|
├── docker-compose.yml # Single-service compose
|
||||||
|
├── .dockerignore # Build context exclusions
|
||||||
|
├── .env.example # Environment variable template
|
||||||
|
├── backup.sh # Volume backup script
|
||||||
|
├── build/ # (gitignored) Production build output
|
||||||
|
└── data/ # (gitignored) Local dev data
|
||||||
|
├── db/
|
||||||
|
│ └── taskplaner.db
|
||||||
|
└── uploads/
|
||||||
|
├── originals/
|
||||||
|
└── thumbnails/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 1: Multi-Stage Dockerfile
|
||||||
|
**What:** Separate build dependencies from runtime to minimize image size
|
||||||
|
**When to use:** Always for production Docker deployments
|
||||||
|
**Example:**
|
||||||
|
```dockerfile
|
||||||
|
# Source: https://gist.github.com/aradalvand/04b2cad14b00e5ffe8ec96a3afbb34fb
|
||||||
|
# Stage 1: Build
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
RUN npm prune --production
|
||||||
|
|
||||||
|
# Stage 2: Production
|
||||||
|
FROM node:22-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/build build/
|
||||||
|
COPY --from=builder /app/node_modules node_modules/
|
||||||
|
COPY package.json .
|
||||||
|
USER node
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
CMD ["node", "build"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: SvelteKit Health Check Endpoint
|
||||||
|
**What:** API route returning 200 for container health checks
|
||||||
|
**When to use:** Docker HEALTHCHECK, Kubernetes probes, load balancer checks
|
||||||
|
**Example:**
|
||||||
|
```typescript
|
||||||
|
// src/routes/health/+server.ts
|
||||||
|
// Source: https://svelte.dev/docs/kit/routing
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async () => {
|
||||||
|
return new Response('ok', { status: 200 });
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Runtime Environment Configuration
|
||||||
|
**What:** Configure app behavior via environment variables at runtime
|
||||||
|
**When to use:** All deployments - allows same image for different environments
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# adapter-node reads these automatically
|
||||||
|
# Source: https://svelte.dev/docs/kit/adapter-node
|
||||||
|
PORT=3000
|
||||||
|
HOST=0.0.0.0
|
||||||
|
ORIGIN=http://localhost:3000
|
||||||
|
BODY_SIZE_LIMIT=10M
|
||||||
|
|
||||||
|
# Reverse proxy headers (when behind nginx/traefik)
|
||||||
|
PROTOCOL_HEADER=x-forwarded-proto
|
||||||
|
HOST_HEADER=x-forwarded-host
|
||||||
|
ADDRESS_HEADER=x-forwarded-for
|
||||||
|
XFF_DEPTH=1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 4: Named Volume for Data Persistence
|
||||||
|
**What:** Docker-managed volume mounted to container data directory
|
||||||
|
**When to use:** Any stateful data (database, uploads)
|
||||||
|
**Example:**
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
volumes:
|
||||||
|
- taskplaner_data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
taskplaner_data:
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns to Avoid
|
||||||
|
- **Copying node_modules from host:** Native modules compiled for host OS won't work in Alpine container. Always run npm install inside Dockerfile.
|
||||||
|
- **Bind mounts for production data:** Use named volumes for better portability and Docker management.
|
||||||
|
- **Running as root:** Security risk. Use `USER node` in production stage.
|
||||||
|
- **Hardcoded configuration:** Makes the image environment-specific. Use env vars.
|
||||||
|
- **Installing dev dependencies in production:** Bloats image. Use `npm prune --production` or `npm ci --only=production`.
|
||||||
|
|
||||||
|
## Don't Hand-Roll
|
||||||
|
|
||||||
|
Problems that look simple but have existing solutions:
|
||||||
|
|
||||||
|
| Problem | Don't Build | Use Instead | Why |
|
||||||
|
|---------|-------------|-------------|-----|
|
||||||
|
| Environment variables | Custom config parser | adapter-node env vars | Built-in, documented, handles edge cases |
|
||||||
|
| Graceful shutdown | Signal handlers | adapter-node SHUTDOWN_TIMEOUT | Built-in, handles request draining |
|
||||||
|
| Health checks | Complex readiness probe | Simple /health route | Docker just needs HTTP 200 |
|
||||||
|
| Process management | supervisor/pm2 | Docker restart policies | Container-native, simpler |
|
||||||
|
| Logging to files | File appenders | stdout/stderr | Docker captures, aggregators consume |
|
||||||
|
|
||||||
|
**Key insight:** adapter-node provides most production concerns out of the box. Don't add middleware or wrappers - use its environment variables.
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Native Module Architecture Mismatch
|
||||||
|
**What goes wrong:** App crashes with "Cannot find module" or "symbol not found" errors for better-sqlite3 or sharp
|
||||||
|
**Why it happens:** node_modules from macOS/Windows host copied to Linux container
|
||||||
|
**How to avoid:**
|
||||||
|
1. Add `node_modules` to `.dockerignore`
|
||||||
|
2. Run `npm ci` inside Dockerfile builder stage
|
||||||
|
3. For sharp on Alpine, npm will download musl-compatible prebuilt binaries automatically (sharp >= 0.33)
|
||||||
|
**Warning signs:** Errors mentioning `linuxmusl`, `glibc`, or module loading failures
|
||||||
|
|
||||||
|
### Pitfall 2: SQLite WAL File Permissions
|
||||||
|
**What goes wrong:** "database is locked" or "read-only" errors
|
||||||
|
**Why it happens:** SQLite creates `-wal` and `-shm` files with same permissions as main db file. If user/group mismatch, can't access.
|
||||||
|
**How to avoid:**
|
||||||
|
1. Run container as `node` user (uid 1000)
|
||||||
|
2. Use named volumes (Docker manages permissions)
|
||||||
|
3. Ensure app creates data directories with correct ownership
|
||||||
|
**Warning signs:** Works first time, fails on restart; "attempt to write a readonly database"
|
||||||
|
|
||||||
|
### Pitfall 3: ORIGIN Environment Variable Missing
|
||||||
|
**What goes wrong:** CSRF errors on form submissions: "Cross-site POST form submissions are forbidden"
|
||||||
|
**Why it happens:** SvelteKit validates Origin header against expected origin
|
||||||
|
**How to avoid:** Always set `ORIGIN` env var to match how users access the app
|
||||||
|
**Warning signs:** Forms work in development but fail in production
|
||||||
|
|
||||||
|
### Pitfall 4: Missing Reverse Proxy Headers
|
||||||
|
**What goes wrong:** App sees internal Docker IPs instead of real client IPs; redirects go to wrong protocol/host
|
||||||
|
**Why it happens:** Default behavior ignores X-Forwarded-* headers for security
|
||||||
|
**How to avoid:** Set `PROTOCOL_HEADER`, `HOST_HEADER`, `ADDRESS_HEADER` only when behind trusted proxy
|
||||||
|
**Warning signs:** Logs show 172.x.x.x instead of real IPs; HTTPS redirects to HTTP
|
||||||
|
|
||||||
|
### Pitfall 5: Volume Data Not Persisting
|
||||||
|
**What goes wrong:** Data disappears after container restart
|
||||||
|
**Why it happens:** Using anonymous volumes or not declaring volume in compose file
|
||||||
|
**How to avoid:**
|
||||||
|
1. Declare named volume in `volumes:` section of docker-compose.yml
|
||||||
|
2. Use consistent mount path in container
|
||||||
|
3. Don't use `docker-compose down -v` unless intentionally deleting data
|
||||||
|
**Warning signs:** Fresh database on each restart
|
||||||
|
|
||||||
|
### Pitfall 6: Health Check Curl Not Installed
|
||||||
|
**What goes wrong:** Container marked unhealthy despite app running
|
||||||
|
**Why it happens:** Alpine doesn't include curl by default
|
||||||
|
**How to avoid:** Use wget (included in Alpine) or node for health checks
|
||||||
|
**Warning signs:** Container in "unhealthy" state but app responds normally
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
Verified patterns from official sources:
|
||||||
|
|
||||||
|
### svelte.config.js with adapter-node
|
||||||
|
```javascript
|
||||||
|
// Source: https://svelte.dev/docs/kit/adapter-node
|
||||||
|
import adapter from '@sveltejs/adapter-node';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
kit: {
|
||||||
|
adapter: adapter({
|
||||||
|
out: 'build',
|
||||||
|
precompress: true,
|
||||||
|
envPrefix: 'TASKPLANER_'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complete Dockerfile
|
||||||
|
```dockerfile
|
||||||
|
# Stage 1: Build
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files first for layer caching
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source and build
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Remove dev dependencies
|
||||||
|
RUN npm prune --production
|
||||||
|
|
||||||
|
# Stage 2: Production
|
||||||
|
FROM node:22-alpine
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy built app and production dependencies
|
||||||
|
COPY --from=builder /app/build build/
|
||||||
|
COPY --from=builder /app/node_modules node_modules/
|
||||||
|
COPY package.json .
|
||||||
|
|
||||||
|
# Create data directory and set ownership
|
||||||
|
RUN mkdir -p /data/db /data/uploads/originals /data/uploads/thumbnails \
|
||||||
|
&& chown -R node:node /data /app
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER node
|
||||||
|
|
||||||
|
# Environment defaults
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV TASKPLANER_DATA_DIR=/data
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Health check using wget (available in Alpine)
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
||||||
|
|
||||||
|
CMD ["node", "build"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### docker-compose.yml
|
||||||
|
```yaml
|
||||||
|
# Source: https://docs.docker.com/reference/compose-file/
|
||||||
|
services:
|
||||||
|
taskplaner:
|
||||||
|
build: .
|
||||||
|
container_name: taskplaner
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT:-3000}:3000"
|
||||||
|
volumes:
|
||||||
|
- taskplaner_data:/data
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
- TASKPLANER_DATA_DIR=/data
|
||||||
|
- ORIGIN=${ORIGIN:-http://localhost:3000}
|
||||||
|
# Uncomment when behind reverse proxy:
|
||||||
|
# - PROTOCOL_HEADER=x-forwarded-proto
|
||||||
|
# - HOST_HEADER=x-forwarded-host
|
||||||
|
# - ADDRESS_HEADER=x-forwarded-for
|
||||||
|
# - XFF_DEPTH=1
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
taskplaner_data:
|
||||||
|
```
|
||||||
|
|
||||||
|
### .dockerignore
|
||||||
|
```
|
||||||
|
# Source: https://snyk.io/blog/10-best-practices-to-containerize-nodejs-web-applications-with-docker/
|
||||||
|
# Dependencies - rebuild inside container
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
build
|
||||||
|
.svelte-kit
|
||||||
|
|
||||||
|
# Development files
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.md
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# IDE/Editor
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Docker files (prevent recursion)
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
docker-compose*.yml
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
*.test.ts
|
||||||
|
*.spec.ts
|
||||||
|
coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
### .env.example
|
||||||
|
```bash
|
||||||
|
# TaskPlaner Environment Configuration
|
||||||
|
# Copy to .env and customize for your deployment
|
||||||
|
|
||||||
|
# === Server Configuration ===
|
||||||
|
# Port the server listens on (inside container, map with docker-compose ports)
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# === Data Storage ===
|
||||||
|
# Directory for database and uploads (must match volume mount)
|
||||||
|
TASKPLANER_DATA_DIR=/data
|
||||||
|
|
||||||
|
# === Production URL ===
|
||||||
|
# Required: The full URL where users access the app
|
||||||
|
# Used for CSRF validation and generating absolute URLs
|
||||||
|
ORIGIN=http://localhost:3000
|
||||||
|
|
||||||
|
# === Reverse Proxy (uncomment when behind nginx/traefik) ===
|
||||||
|
# PROTOCOL_HEADER=x-forwarded-proto
|
||||||
|
# HOST_HEADER=x-forwarded-host
|
||||||
|
# ADDRESS_HEADER=x-forwarded-for
|
||||||
|
# XFF_DEPTH=1
|
||||||
|
|
||||||
|
# === Request Limits ===
|
||||||
|
# Maximum request body size (default: 512kb)
|
||||||
|
# Supports K, M, G suffixes
|
||||||
|
BODY_SIZE_LIMIT=10M
|
||||||
|
|
||||||
|
# === Logging ===
|
||||||
|
# Log level: debug, info, warn, error
|
||||||
|
# TASKPLANER_LOG_LEVEL=info
|
||||||
|
```
|
||||||
|
|
||||||
|
### backup.sh
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Source: https://docs.docker.com/engine/storage/volumes/
|
||||||
|
# Backup TaskPlaner data volume
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BACKUP_DIR="${BACKUP_DIR:-./backups}"
|
||||||
|
CONTAINER_NAME="${CONTAINER_NAME:-taskplaner}"
|
||||||
|
VOLUME_NAME="${VOLUME_NAME:-taskplaner_data}"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
BACKUP_FILE="${BACKUP_DIR}/taskplaner_backup_${TIMESTAMP}.tar.gz"
|
||||||
|
|
||||||
|
# Create backup directory if needed
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
echo "Backing up TaskPlaner data..."
|
||||||
|
echo "Volume: $VOLUME_NAME"
|
||||||
|
echo "Output: $BACKUP_FILE"
|
||||||
|
|
||||||
|
# Create backup using temporary container
|
||||||
|
docker run --rm \
|
||||||
|
-v "${VOLUME_NAME}:/data:ro" \
|
||||||
|
-v "$(pwd)/${BACKUP_DIR}:/backup" \
|
||||||
|
alpine:latest \
|
||||||
|
tar czf "/backup/taskplaner_backup_${TIMESTAMP}.tar.gz" -C /data .
|
||||||
|
|
||||||
|
echo "Backup complete: $BACKUP_FILE"
|
||||||
|
echo "Size: $(du -h "$BACKUP_FILE" | cut -f1)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check Endpoint
|
||||||
|
```typescript
|
||||||
|
// src/routes/health/+server.ts
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
import { db } from '$lib/server/db';
|
||||||
|
import { entries } from '$lib/server/db/schema';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async () => {
|
||||||
|
try {
|
||||||
|
// Verify database connectivity
|
||||||
|
db.select().from(entries).limit(1).all();
|
||||||
|
return new Response('ok', {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'text/plain' }
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return new Response('unhealthy', {
|
||||||
|
status: 503,
|
||||||
|
headers: { 'Content-Type': 'text/plain' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## State of the Art
|
||||||
|
|
||||||
|
| Old Approach | Current Approach | When Changed | Impact |
|
||||||
|
|--------------|------------------|--------------|--------|
|
||||||
|
| adapter-auto | adapter-node for Docker | SvelteKit 2.x | Explicit Node.js server, env var support |
|
||||||
|
| COPY node_modules | npm ci in Dockerfile | Always best practice | Correct native module binaries |
|
||||||
|
| curl in healthcheck | wget (Alpine native) | Alpine best practice | No extra packages needed |
|
||||||
|
| Root user | node:node (uid 1000) | Security standard | Reduced attack surface |
|
||||||
|
| JSON config files | Environment variables | 12-factor app | Same image, different configs |
|
||||||
|
|
||||||
|
**Deprecated/outdated:**
|
||||||
|
- `adapter-node@1.x`: Use 5.x for SvelteKit 2 compatibility
|
||||||
|
- Installing sharp with `--platform` flags: sharp 0.33+ auto-detects musl
|
||||||
|
- Manual signal handling: adapter-node handles SIGTERM/SIGINT natively
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
Things that couldn't be fully resolved:
|
||||||
|
|
||||||
|
1. **JSON logging integration**
|
||||||
|
- What we know: Pino is the standard for JSON logging in Node.js
|
||||||
|
- What's unclear: Pino has bundling issues with SvelteKit production builds (Worker Threads)
|
||||||
|
- Recommendation: Defer structured logging to a future enhancement. Console.log output goes to stdout and Docker captures it. Can add pino later if needed.
|
||||||
|
|
||||||
|
2. **Drizzle migrations in production**
|
||||||
|
- What we know: Currently using `drizzle-kit push` for development
|
||||||
|
- What's unclear: Best practice for running migrations in Docker container
|
||||||
|
- Recommendation: For single-user app, continue using push mode via db initialization in hooks.server.ts. Schema is simple enough. Could add migration script later if needed.
|
||||||
|
|
||||||
|
3. **Specific extended environment variables**
|
||||||
|
- What we know: CONTEXT.md says "extended config scope" beyond PORT/DATA_DIR
|
||||||
|
- What's unclear: Exactly which additional env vars users need
|
||||||
|
- Recommendation: Start with essentials (PORT, DATA_DIR, ORIGIN, BODY_SIZE_LIMIT, log level). Add more based on user feedback.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- [SvelteKit adapter-node documentation](https://svelte.dev/docs/kit/adapter-node) - Environment variables, configuration, graceful shutdown
|
||||||
|
- [Docker official volumes documentation](https://docs.docker.com/engine/storage/volumes/) - Named volumes, backup procedures
|
||||||
|
- [Docker Compose services documentation](https://docs.docker.com/reference/compose-file/services/) - Health checks, configuration
|
||||||
|
- [Sharp installation documentation](https://sharp.pixelplumbing.com/install/) - Cross-platform, musl support
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- [SvelteKit Dockerfile gist by aradalvand](https://gist.github.com/aradalvand/04b2cad14b00e5ffe8ec96a3afbb34fb) - Multi-stage build pattern
|
||||||
|
- [Snyk Node.js Docker best practices](https://snyk.io/blog/10-best-practices-to-containerize-nodejs-web-applications-with-docker/) - Security, .dockerignore
|
||||||
|
- [Node.js docker-node best practices](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md) - Non-root user, Alpine considerations
|
||||||
|
|
||||||
|
### Tertiary (LOW confidence)
|
||||||
|
- Various Medium/DEV articles on SvelteKit Docker deployment - General patterns confirmed by official docs
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Confidence breakdown:**
|
||||||
|
- Standard stack: HIGH - adapter-node is official, well-documented
|
||||||
|
- Architecture: HIGH - Multi-stage builds, named volumes are established Docker patterns
|
||||||
|
- Pitfalls: HIGH - Native modules, SQLite permissions are well-known issues with documented solutions
|
||||||
|
- Code examples: HIGH - Based on official documentation
|
||||||
|
|
||||||
|
**Research date:** 2026-02-01
|
||||||
|
**Valid until:** 2026-03-01 (30 days - Docker/SvelteKit patterns are stable)
|
||||||
Reference in New Issue
Block a user