Phase 06: Deployment - 2 plans in 2 waves - Wave 1: Docker configuration (adapter-node, Dockerfile, docker-compose) - Wave 2: Runtime configuration (health endpoint, env docs, backup script) - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.2 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-deployment | 01 | execute | 1 |
|
true |
|
Purpose: Enable the application to run in a Docker container with proper production settings, multi-stage build for small image size, and non-root user for security.
Output: Dockerfile, docker-compose.yml, .dockerignore, and updated svelte.config.js with adapter-node
<execution_context> @/home/tho/.claude/get-shit-done/workflows/execute-plan.md @/home/tho/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-deployment/06-RESEARCH.mdKey existing patterns:
- Database at ./data/taskplaner.db with WAL mode
- Image storage at ./data/uploads/originals and ./data/uploads/thumbnails
- DATABASE_PATH env var already used in src/lib/server/db/index.ts
- Update svelte.config.js:
- Import adapter from '@sveltejs/adapter-node' (not adapter-auto)
- Configure adapter with:
- out: 'build'
- precompress: true (gzip/brotli static assets)
- envPrefix: 'TASKPLANER_' (custom env var prefix)
Example:
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;
NOTE: Keep adapter-auto in devDependencies as fallback for non-Docker dev environments. adapter-node is what the Dockerfile will use.
- npm run build completes successfully
- build/ directory is created with server files
- ls build/ shows index.js, handler.js, etc.
svelte.config.js uses adapter-node, npm run build produces build/ directory
Build outputs
build .svelte-kit
Development/local data
data .git .gitignore .env .env.*
Documentation
*.md
IDE/Editor
.vscode .idea
Logs
*.log
Docker files (prevent recursion)
Dockerfile .dockerignore docker-compose*.yml
Planning docs
.planning
2. Create Dockerfile with multi-stage build:
```dockerfile
# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
# Copy package files for layer caching
COPY package*.json ./
RUN npm ci
# Copy source and build
COPY . .
RUN npm run build
# Remove dev dependencies for smaller production image
RUN npm prune --production
# Stage 2: Production
FROM node:22-alpine
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 directories and set ownership
# App expects /data/db for database and /data/uploads for images
RUN mkdir -p /data/db /data/uploads/originals /data/uploads/thumbnails \
&& chown -R node:node /data /app
# Switch to non-root user for security
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, curl is not)
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"]
- Create docker-compose.yml:
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} - BODY_SIZE_LIMIT=10M # Uncomment when behind reverse proxy (nginx/traefik): # - PROTOCOL_HEADER=x-forwarded-proto # - HOST_HEADER=x-forwarded-host # - ADDRESS_HEADER=x-forwarded-for # - XFF_DEPTH=1 volumes: taskplaner_data:
IMPORTANT: The Dockerfile HEALTHCHECK references /health endpoint which will be created in Plan 02. The container will show "unhealthy" until Plan 02 adds that endpoint, but the app will still run.
- docker build -t taskplaner . completes without errors
- Image size is under 250MB: docker images taskplaner
- docker-compose config shows valid configuration
Dockerfile builds successfully, docker-compose.yml is valid YAML, .dockerignore excludes node_modules and build outputs
// Data directory from env (Docker: /data, local: ./data) const DATA_DIR = process.env.TASKPLANER_DATA_DIR || './data'; const DB_PATH = process.env.DATABASE_PATH || join(DATA_DIR, 'db', 'taskplaner.db');
// Ensure data directory exists const dbDir = dirname(DB_PATH); if (!existsSync(dbDir)) { mkdirSync(dbDir, { recursive: true }); }
const sqlite = new Database(DB_PATH);
// Enable WAL mode for better concurrent read performance sqlite.pragma('journal_mode = WAL');
export const db = drizzle(sqlite, { schema });
export { schema };
2. Update src/lib/server/images/storage.ts to use TASKPLANER_DATA_DIR:
```typescript
import { mkdir, writeFile, unlink } from 'node:fs/promises';
import { join } from 'node:path';
// Data directory from env (Docker: /data, local: ./data)
const DATA_DIR = process.env.TASKPLANER_DATA_DIR || './data';
export const UPLOAD_DIR = join(DATA_DIR, 'uploads');
export const ORIGINALS_DIR = join(DATA_DIR, 'uploads', 'originals');
export const THUMBNAILS_DIR = join(DATA_DIR, 'uploads', 'thumbnails');
// Rest of file unchanged...
This makes paths configurable:
- Local development: Uses ./data (default)
- Docker: Uses /data (from TASKPLANER_DATA_DIR env var)
- DATABASE_PATH still works as override for backward compatibility
npm run devstill works (uses ./data default)npm run buildcompletes without type errors- Database and uploads work in development mode Data paths read from TASKPLANER_DATA_DIR env var with ./data fallback for local development
<success_criteria>
- svelte.config.js uses adapter-node with TASKPLANER_ prefix
- Dockerfile uses multi-stage build with node:22-alpine
- Container runs as non-root 'node' user
- docker-compose.yml starts app with named volume for /data
- Data paths are configurable via TASKPLANER_DATA_DIR
- Local development still works with default ./data paths </success_criteria>