Devlog Mar 13: Production Hardening
This week we focused on production hardening, implementing critical safety measures for database operations, separating development and production module configurations, and fixing authentication issues for HTTP deployments.
Database Migration Safety
Banning drizzle-kit generate
To prevent accidental schema changes in production, we've banned the drizzle-kit generate command:
#!/bin/bash
# scripts/db/generate.sh
echo "Error: drizzle-kit generate is banned in production."
echo ""
echo "This command can modify the database schema and should only be run"
echo "in development environments after careful testing."
echo ""
echo "For production migrations, use:"
echo " npm run db:migrate"
echo ""
echo "If you need to create a new migration:"
echo " 1. Create the migration SQL file manually in ./migrations/"
echo " 2. Run 'npm run db:migrate' to apply it"
echo " 3. Test thoroughly in development"
echo " 4. Submit a pull request for review"
exit 1
Migration Creation Script Disabled
We disabled the automated migration creation script to ensure all migrations go through proper review:
// scripts/db/create-migration.ts (disabled)
/*
export async function createMigration(name: string) {
console.error('Migration creation is disabled in production');
console.error('Please create migrations manually following the guidelines');
process.exit(1);
}
*/
This ensures:
- All migrations are manually crafted
- Schema changes are reviewed before deployment
- Production databases remain stable
- No accidental schema drift
Module Configuration Separation
Dev vs Prod Module Configs
We separated development and production module configurations for better control:
// modules.config.ts
export const modulesConfig = {
development: {
modules: [
{
id: 'molos-tasks',
repository: 'https://github.com/MoLOS-App/Modules/MoLOS-Tasks',
branch: 'develop', // Use develop branch for testing
enabled: true
},
{
id: 'molos-notes',
repository: 'https://github.com/MoLOS-App/Modules/MoLOS-Notes',
branch: 'develop',
enabled: true
}
]
},
production: {
modules: [
{
id: 'molos-tasks',
repository: 'https://github.com/MoLOS-App/Modules/MoLOS-Tasks',
tag: 'v1.0.2', // Pinned to stable version
enabled: true
},
{
id: 'molos-notes',
repository: 'https://github.com/MoLOS-App/Modules/MoLOS-Notes',
tag: 'v1.0.0',
enabled: true
}
]
}
};
export function getModulesConfig(): ModuleConfig {
const env = process.env.NODE_ENV || 'development';
return modulesConfig[env] || modulesConfig.development;
}
Build Configuration
Docker builds use production configuration:
# Dockerfile.production
FROM node:20-alpine AS production
# Set production environment
ENV NODE_ENV=production
# Copy production modules configuration
COPY modules.config.ts ./
COPY tsconfig.json ./
# Install only production dependencies
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && \
pnpm install --frozen-lockfile --prod=false
# Build with production modules
RUN pnpm build
# ...
Configuration Validation
We added validation to ensure production uses correct module versions:
// scripts/validate-production.ts
export async function validateProductionConfig() {
const config = getModulesConfig();
if (process.env.NODE_ENV === 'production') {
for (const module of config.modules) {
if (module.branch && !module.tag) {
throw new Error(
`Production deployment using branch '${module.branch}' for module '${module.id}'. ` +
`Please use pinned tags instead.`
);
}
if (module.tag && !module.tag.match(/^v\d+\.\d+\.\d+$/)) {
throw new Error(
`Invalid version tag '${module.tag}' for module '${module.id}'. ` +
`Tags must follow semantic versioning (e.g., v1.0.0).`
);
}
}
}
console.log('✓ Production configuration is valid');
}
HTTP Deployment Authentication
ORIGIN Environment Variable
We fixed authentication issues for HTTP deployments by using a single ORIGIN environment variable:
// server/auth/index.ts
import { auth } from 'better-auth/server';
const ORIGIN = process.env.ORIGIN || 'http://localhost:5173';
const betterAuth = auth({
secret: process.env.BETTER_AUTH_SECRET!,
trustedOrigins: [ORIGIN],
advanced: {
cookiePrefix: 'molos',
useSecureCookies: ORIGIN.startsWith('https://')
}
});
Environment Configuration
The ORIGIN variable now handles both HTTP and HTTPS deployments:
# Development (HTTP)
ORIGIN=http://localhost:5173
# Production (HTTPS)
ORIGIN=https://molos.example.com
# Production (HTTP - not recommended)
ORIGIN=http://molos.example.com
Cookie Security
Cookies are configured appropriately based on the origin:
// server/auth/cookies.ts
function getCookieConfig(origin: string) {
return {
httpOnly: true,
secure: origin.startsWith('https://'),
sameSite: 'lax' as const,
maxAge: 60 * 60 * 24 * 7 // 7 days
};
}
Docker Security Enhancements
Privilege Dropping
Enhanced privilege dropping with gosu:
#!/usr/bin/env bash
# docker-entrypoint.sh
# Set up non-root user
if [ "$(id -u)" = '0' ]; then
# Fix permissions on data directory
chown -R molos:molos /data
# Drop to non-root user
exec gosu molos "$@"
fi
# Run as molos user
exec "$@"
Immutable Filesystem
The production container uses an immutable filesystem:
# All files are owned by root
COPY --chown=root:root . /app
# Application runs as molos user
RUN addgroup -g 1001 -S molos && \
adduser -S -u 1001 -G molos molos
# Only /data directory is writable
VOLUME ["/data"]
# Use entrypoint that drops privileges
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "dist/index.js"]
Minimal Attack Surface
The production image has minimal dependencies:
FROM node:20-alpine AS base
# Only install essential system packages
RUN apk add --no-cache \
ca-certificates \
dumb-init \
gosu
# Remove build tools from final image
RUN apk del --purge \
build-base \
git \
python3 \
make
Production Monitoring
Health Check Endpoints
Enhanced health check endpoints for monitoring:
// server/api/health/+server.ts
export async function GET({ request }: RequestEvent) {
const checks = {
database: await checkDatabase(),
modules: await checkModules(),
auth: await checkAuth(),
migrations: await checkMigrations()
};
const healthy = Object.values(checks).every(check => check.healthy);
return new Response(
JSON.stringify({
status: healthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
checks
}),
{
status: healthy ? 200 : 503,
headers: {
'Content-Type': 'application/json'
}
}
);
}
Readiness Probes
Docker health checks:
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
Security Headers
We implemented comprehensive security headers:
// server/hooks.ts
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event);
const headers = new Headers(response.headers);
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('X-Frame-Options', 'DENY');
headers.set('X-XSS-Protection', '1; mode=block');
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
if (event.request.url.startsWith('https://')) {
headers.set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
}
return new Response(response.body, {
status: response.status,
headers
});
};
Key Commits
- chore(db): ban drizzle-kit generate command
- chore(db): disable migration creation script
- build(modules): separate dev and prod module configurations
- Fix auth for HTTP deployments - use single ORIGIN env var
- build(docker): add gosu for proper privilege dropping in container entrypoint
What's Next
Production hardening continues with:
- Implementing rate limiting
- Adding intrusion detection
- Enhancing logging and audit trails
- Implementing automated security scanning
- Adding backup and recovery procedures
These production hardening measures significantly improve the security and stability of MoLOS deployments.
