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.