Skip to main content

Devlog Mar 13: Production Hardening

· 6 min read
Eduardez
MoLOS Lead Developer

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

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

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.