mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-08-17 21:25:52 +02:00
Docker configuration, build file for app
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# Development Environment Configuration
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# Database Configuration
|
||||
POSTGRES_DB=appointment_booking
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=your_secure_password_here
|
||||
POSTGRES_PORT=5432
|
||||
|
||||
# Application Configuration
|
||||
NODE_ENV=development
|
||||
APP_PORT=5173
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
node_modules/
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
build/
|
||||
dist/
|
||||
.svelte-kit/
|
||||
|
||||
# Production secrets - NEVER commit these
|
||||
secrets/*.txt
|
||||
!secrets/.gitkeep
|
||||
|
||||
# Development
|
||||
.DS_Store
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,31 @@
|
||||
# Production Caddyfile for appointment booking application
|
||||
# Replace your-domain.com with your actual domain
|
||||
|
||||
your-domain.com {
|
||||
reverse_proxy app:3000
|
||||
|
||||
# Security headers
|
||||
header {
|
||||
-Server
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "DENY"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'"
|
||||
}
|
||||
|
||||
encode gzip
|
||||
|
||||
log {
|
||||
output file /data/access.log {
|
||||
roll_size 100mb
|
||||
roll_keep 10
|
||||
}
|
||||
format json
|
||||
}
|
||||
|
||||
tls {
|
||||
protocols tls1.2 tls1.3
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Production Dockerfile for SvelteKit application
|
||||
# Security: rootless container, minimal base image, non-privileged user
|
||||
|
||||
# Build stage
|
||||
FROM node:24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production --ignore-scripts && \
|
||||
npm cache clean --force
|
||||
|
||||
# Copy source and build
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM node:24-alpine AS production
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1001 -S nodejs && \
|
||||
adduser -S sveltekit -u 1001 -G nodejs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built application and dependencies
|
||||
COPY --from=builder --chown=sveltekit:nodejs /app/build ./build
|
||||
COPY --from=builder --chown=sveltekit:nodejs /app/package*.json ./
|
||||
COPY --from=builder --chown=sveltekit:nodejs /app/node_modules ./node_modules
|
||||
|
||||
# Create logs directory
|
||||
RUN mkdir -p /app/logs && \
|
||||
chown -R sveltekit:nodejs /app
|
||||
|
||||
# Drop privileges
|
||||
USER sveltekit
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
||||
|
||||
# Start application
|
||||
CMD ["node", "build/index.js"]
|
||||
@@ -0,0 +1,36 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: appointment-booking-postgres-dev
|
||||
restart: unless-stopped
|
||||
user: postgres
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-appointment_booking}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- postgres_data_dev:/var/lib/postgresql/data
|
||||
- ./init-db:/docker-entrypoint-initdb.d:ro
|
||||
networks:
|
||||
- appointment-booking-dev
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-appointment_booking}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /var/run/postgresql
|
||||
|
||||
volumes:
|
||||
postgres_data_dev:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
appointment-booking-dev:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,120 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: appointment-booking-postgres
|
||||
restart: unless-stopped
|
||||
user: postgres
|
||||
environment:
|
||||
POSTGRES_DB_FILE: /run/secrets/postgres_db
|
||||
POSTGRES_USER_FILE: /run/secrets/postgres_user
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256 --auth-local=scram-sha-256"
|
||||
secrets:
|
||||
- postgres_db
|
||||
- postgres_user
|
||||
- postgres_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./init-db:/docker-entrypoint-initdb.d:ro
|
||||
networks:
|
||||
- appointment-booking-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$(cat /run/secrets/postgres_user) -d $$(cat /run/secrets/postgres_db)"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /var/run/postgresql
|
||||
read_only: true
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: production
|
||||
container_name: appointment-booking-app
|
||||
restart: unless-stopped
|
||||
user: "1001:1001"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
POSTGRES_DB_FILE: /run/secrets/postgres_db
|
||||
POSTGRES_USER_FILE: /run/secrets/postgres_user
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
||||
secrets:
|
||||
- postgres_db
|
||||
- postgres_user
|
||||
- postgres_password
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- appointment-booking-internal
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
tmpfs:
|
||||
- /tmp
|
||||
read_only: true
|
||||
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: appointment-booking-caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
networks:
|
||||
- appointment-booking-internal
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
|
||||
secrets:
|
||||
postgres_db:
|
||||
file: ./secrets/postgres_db.txt
|
||||
postgres_user:
|
||||
file: ./secrets/postgres_user.txt
|
||||
postgres_password:
|
||||
file: ./secrets/postgres_password.txt
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
caddy_data:
|
||||
driver: local
|
||||
caddy_config:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
appointment-booking-internal:
|
||||
driver: bridge
|
||||
internal: false
|
||||
@@ -0,0 +1,338 @@
|
||||
# Infrastructure Setup
|
||||
|
||||
This document describes the complete setup for the appointment booking application infrastructure.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The application consists of three Docker containers:
|
||||
|
||||
1. **PostgreSQL Database** - Data persistence
|
||||
2. **SvelteKit Application** - Web application (production only)
|
||||
3. **Caddy Reverse Proxy** - HTTPS termination and routing (production only)
|
||||
|
||||
## Development Setup
|
||||
|
||||
Development runs only the PostgreSQL database with a public port for development tools.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker and Docker Compose
|
||||
- Node.js 24+
|
||||
- Git
|
||||
|
||||
### Setup Steps
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd appointment-booking-software
|
||||
```
|
||||
|
||||
2. **Configure environment**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
Edit `.env` and set secure values:
|
||||
```
|
||||
POSTGRES_DB=appointment_booking
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=your_secure_development_password
|
||||
POSTGRES_PORT=5432
|
||||
```
|
||||
|
||||
3. **Start development database**
|
||||
```bash
|
||||
npm run docker:dev:up
|
||||
```
|
||||
|
||||
4. **Verify database connection**
|
||||
```bash
|
||||
# Check container status
|
||||
docker ps
|
||||
|
||||
# Test connection
|
||||
psql -h localhost -U postgres -d appointment_booking
|
||||
```
|
||||
|
||||
5. **Install SvelteKit dependencies** (when SvelteKit code is added)
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
6. **Start SvelteKit development server** (when SvelteKit code is added)
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Development Commands
|
||||
|
||||
```bash
|
||||
# Database management
|
||||
npm run docker:dev:up # Start PostgreSQL
|
||||
npm run docker:dev:down # Stop PostgreSQL
|
||||
npm run docker:dev:logs # View database logs
|
||||
npm run docker:dev:clean # Remove containers and volumes
|
||||
|
||||
# Application (when SvelteKit is implemented)
|
||||
npm run dev # Start development server
|
||||
npm run build # Build for production
|
||||
```
|
||||
|
||||
## Production Setup
|
||||
|
||||
Production runs all three containers with security hardening and HTTPS.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker and Docker Compose
|
||||
- Domain name with DNS pointing to server
|
||||
- Server with public IP
|
||||
|
||||
### Setup Steps
|
||||
|
||||
1. **Configure production secrets**
|
||||
|
||||
Edit the secret files in the `secrets/` directory:
|
||||
```bash
|
||||
# Database configuration
|
||||
echo "appointment_booking" > secrets/postgres_db.txt
|
||||
echo "postgres" > secrets/postgres_user.txt
|
||||
echo "$(openssl rand -base64 32)" > secrets/postgres_password.txt
|
||||
|
||||
# Set proper permissions
|
||||
chmod 600 secrets/*.txt
|
||||
```
|
||||
|
||||
2. **Configure domain**
|
||||
|
||||
Edit `Caddyfile` and replace `your-domain.com` with your actual domain:
|
||||
```
|
||||
yourdomain.com {
|
||||
# ... rest of configuration
|
||||
}
|
||||
```
|
||||
|
||||
3. **Build and start production stack**
|
||||
```bash
|
||||
# Build application image
|
||||
npm run docker:prod:build
|
||||
|
||||
# Start all services
|
||||
npm run docker:prod:up
|
||||
```
|
||||
|
||||
4. **Verify deployment**
|
||||
```bash
|
||||
# Check all containers are running
|
||||
docker ps
|
||||
|
||||
# Check application health
|
||||
npm run docker:health
|
||||
|
||||
# View logs
|
||||
npm run docker:prod:logs
|
||||
```
|
||||
|
||||
### Production Commands
|
||||
|
||||
```bash
|
||||
# Stack management
|
||||
npm run docker:prod:build # Build application image
|
||||
npm run docker:prod:up # Start all services
|
||||
npm run docker:prod:down # Stop all services
|
||||
npm run docker:prod:logs # View all logs
|
||||
npm run docker:prod:clean # Remove containers and volumes
|
||||
npm run docker:health # Check container status
|
||||
npm run docker:tag-and-push # Build, tag and push the app image to GHCR (Remember to change username accordingly first)
|
||||
```
|
||||
|
||||
## SvelteKit Application Setup
|
||||
|
||||
When implementing the SvelteKit application, follow these steps:
|
||||
|
||||
### Database Connection
|
||||
|
||||
Create `src/lib/database.js`:
|
||||
```javascript
|
||||
import { readFileSync } from 'fs';
|
||||
import pg from 'pg';
|
||||
|
||||
function getDatabaseConfig() {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// Read from Docker secrets
|
||||
const user = readFileSync('/run/secrets/postgres_user', 'utf8').trim();
|
||||
const password = readFileSync('/run/secrets/postgres_password', 'utf8').trim();
|
||||
const database = readFileSync('/run/secrets/postgres_db', 'utf8').trim();
|
||||
|
||||
return {
|
||||
host: 'postgres',
|
||||
port: 5432,
|
||||
user,
|
||||
password,
|
||||
database
|
||||
};
|
||||
} else {
|
||||
// Development configuration
|
||||
return {
|
||||
host: 'localhost',
|
||||
port: process.env.POSTGRES_PORT || 5432,
|
||||
user: process.env.POSTGRES_USER || 'postgres',
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DB || 'appointment_booking'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const pool = new pg.Pool(getDatabaseConfig());
|
||||
```
|
||||
|
||||
### Health Check Endpoint
|
||||
|
||||
Create `src/routes/health/+server.js`:
|
||||
```javascript
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { pool } from '$lib/database.js';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await pool.query('SELECT 1');
|
||||
return json({ status: 'healthy', timestamp: new Date().toISOString() });
|
||||
} catch (error) {
|
||||
return json(
|
||||
{ status: 'unhealthy', error: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SvelteKit Configuration
|
||||
|
||||
|
||||
Update `vite.config.js`:
|
||||
```javascript
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Security Features
|
||||
|
||||
### Container Security
|
||||
- **Rootless containers**: All containers (sans caddy, which needs to access privileged ports) run as non-root users
|
||||
- **Read-only filesystems**: Containers have read-only root filesystems where possible
|
||||
- **No new privileges**: Containers cannot escalate privileges
|
||||
- **Minimal capabilities**: Only necessary Linux capabilities are granted
|
||||
|
||||
### Network Security
|
||||
- **Internal networks**: Database and app communicate via internal Docker network
|
||||
- **No exposed ports**: Only Caddy exposes ports to the host
|
||||
- **HTTPS only**: Caddy handles automatic HTTPS with Let's Encrypt
|
||||
|
||||
### Data Security
|
||||
- **Docker secrets**: Sensitive data stored as Docker secrets
|
||||
- **Encrypted connections**: Database connections use SSL/TLS
|
||||
- **Security headers**: Comprehensive security headers via Caddy
|
||||
|
||||
## Monitoring and Logs
|
||||
|
||||
### Log Locations
|
||||
- **Caddy logs**: Stored in `/data/access.log` inside Caddy container
|
||||
- **Application logs**: Available via `docker logs`
|
||||
- **Database logs**: Available via `docker logs`
|
||||
|
||||
### Health Checks
|
||||
- **Application**: `GET /health` endpoint
|
||||
- **Database**: Built-in PostgreSQL health check
|
||||
- **Caddy**: HTTP response check
|
||||
|
||||
### Monitoring Commands
|
||||
```bash
|
||||
# View real-time logs
|
||||
npm run docker:prod:logs
|
||||
|
||||
# Check container health
|
||||
docker ps
|
||||
npm run docker:health
|
||||
|
||||
# Individual container logs
|
||||
docker logs appointment-booking-app
|
||||
docker logs appointment-booking-postgres
|
||||
docker logs appointment-booking-caddy
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Container won't start**
|
||||
```bash
|
||||
# Check container logs
|
||||
docker logs <container-name>
|
||||
|
||||
# Check resource usage
|
||||
docker stats
|
||||
```
|
||||
|
||||
2. **Database connection issues**
|
||||
```bash
|
||||
# Test database connectivity
|
||||
docker exec -it appointment-booking-postgres psql -U postgres -d appointment_booking
|
||||
|
||||
# Check database logs
|
||||
docker logs appointment-booking-postgres
|
||||
```
|
||||
|
||||
3. **HTTPS certificate issues**
|
||||
```bash
|
||||
# Check Caddy logs
|
||||
docker logs appointment-booking-caddy
|
||||
|
||||
# Verify domain DNS
|
||||
nslookup yourdomain.com
|
||||
```
|
||||
|
||||
4. **Application not accessible**
|
||||
```bash
|
||||
# Check if all containers are running
|
||||
docker ps
|
||||
|
||||
# Test internal connectivity
|
||||
docker exec -it appointment-booking-caddy wget -q --spider http://app:3000/health
|
||||
```
|
||||
|
||||
### Reset Commands
|
||||
|
||||
```bash
|
||||
# Complete reset (destroys all data)
|
||||
npm run docker:prod:clean
|
||||
npm run docker:dev:clean
|
||||
|
||||
# Rebuild and restart
|
||||
npm run docker:prod:build
|
||||
npm run docker:prod:up
|
||||
```
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
### Database Backup
|
||||
```bash
|
||||
# Create backup
|
||||
docker exec appointment-booking-postgres pg_dump -U postgres appointment_booking > backup.sql
|
||||
|
||||
# Restore backup
|
||||
docker exec -i appointment-booking-postgres psql -U postgres appointment_booking < backup.sql
|
||||
```
|
||||
|
||||
### Volume Backup
|
||||
```bash
|
||||
# Backup PostgreSQL data volume
|
||||
docker run --rm -v appointment-booking-software_postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "appointment-booking-software",
|
||||
"version": "0.0.1",
|
||||
"description": "End-to-end encrypted appointment booking platform",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "echo 'Build placeholder - will be replaced with SvelteKit build'",
|
||||
"docker:dev:up": "docker compose -f docker-compose.dev.yml up -d",
|
||||
"docker:dev:down": "docker compose -f docker-compose.dev.yml down",
|
||||
"docker:dev:logs": "docker compose -f docker-compose.dev.yml logs -f",
|
||||
"docker:dev:clean": "docker compose -f docker-compose.dev.yml down -v --remove-orphans",
|
||||
"docker:prod:build": "docker compose -f docker-compose.prod.yml build --no-cache",
|
||||
"docker:prod:up": "docker compose -f docker-compose.prod.yml up -d",
|
||||
"docker:prod:down": "docker compose -f docker-compose.prod.yml down",
|
||||
"docker:prod:logs": "docker compose -f docker-compose.prod.yml logs -f",
|
||||
"docker:prod:clean": "docker compose -f docker-compose.prod.yml down -v --remove-orphans",
|
||||
"docker:tag-and-push": "npm run docker:prod:build && docker tag appointment-booking-software-app:latest appointment-booking-software:$npm_package_version && docker push ghcr.io/username/appointment-booking-software:$npm_package_version"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.0.0"
|
||||
},
|
||||
"license": "AGPL-3.0"
|
||||
}
|
||||
Reference in New Issue
Block a user