Skip to main content

Devlog Mar 1: Production Build & Docker Optimizations

· 5 min read
Eduardez
MoLOS Lead Developer

This week we focused on optimizing the production build system and improving the Docker container infrastructure. These changes significantly improve build times, reduce image sizes, and enhance the security and reliability of deployments.

Build System Improvements

Automated Release Management with Changesets

We implemented a comprehensive automated release management system using Changesets:

// .changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "develop",
"updateInternalDependencies": "patch",
"ignore": []
}

The Changesets integration provides:

  • Automatic version bumping based on semantic versioning
  • Changelog generation from change descriptions
  • Structured release process across all packages
  • Version synchronization between core and modules

Production Runtime Migration

We migrated the production runtime from bun to Node.js after careful testing:

# Production image
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./package.json
ENV NODE_ENV=production
CMD ["node", "dist/index.js"]

While bun offers faster development and build times, Node.js provides:

  • More stable runtime for production
  • Better compatibility with existing tools and libraries
  • Smaller memory footprint in long-running processes
  • Better debugging and profiling support

Docker Build Optimizations

We restructured the Dockerfile for better layer caching and smaller images:

# Base image with dependencies
FROM node:20-alpine AS base
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN apk add --no-cache ca-certificates && \
corepack enable pnpm && \
pnpm install --frozen-lockfile

# Development build
FROM base AS dev
COPY . .
RUN pnpm build:dev

# Production build
FROM base AS build
COPY . .
RUN pnpm build

Key improvements:

  • Multi-stage builds for separation of concerns
  • Layer caching for faster rebuilds
  • Alpine Linux for minimal image size
  • ca-certificates installed for HTTPS support

Module Handling Improvements

Build-Time Module Resolution

We implemented build-time module resolution to reduce runtime complexity:

// build/utils/resolveModules.ts
export function resolveModules(config: ModuleConfig): ModuleResolution {
const modules: ResolvedModule[] = [];

for (const [name, moduleConfig] of Object.entries(config.modules)) {
const resolved = {
name,
version: moduleConfig.tag || moduleConfig.branch,
source: moduleConfig.tag ? 'tag' : 'branch',
enabled: moduleConfig.enabled,
dependencies: resolveDependencies(moduleConfig)
};
modules.push(resolved);
}

return {
modules,
timestamp: Date.now()
};
}

Benefits:

  • Static analysis of module dependencies
  • Early detection of version conflicts
  • Smaller runtime bundles
  • Faster application startup

Dev vs Prod Image Tags

We introduced separate image tags for development and production:

# Development builds
docker build -t molos-app:dev .
docker build -t molos-app:dev-$(git rev-parse --short HEAD) .

# Production builds
docker build --target production -t molos-app:latest .
docker build --target production -t molos-app:v1.0.0 .

Container Security Hardening

Privilege Dropping with gosu

We added proper privilege dropping to the container entrypoint:

# Install gosu for privilege dropping
RUN apk add --no-cache gosu

# Create non-root user
RUN addgroup -g 1001 -S molos && \
adduser -S -u 1001 -G molos molos

# Copy entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["docker-entrypoint.sh"]
#!/usr/bin/env bash
# docker-entrypoint.sh

# Drop privileges if running as root
if [ "$(id -u)" = '0' ]; then
exec gosu molos "$@"
fi

exec "$@"

Security improvements:

  • Application runs as non-root user
  • Reduced attack surface
  • Compliant with security best practices
  • Better compatibility with orchestration platforms

Workspace Dependency Cleaning

We added workspace dependency cleaning for Docker builds:

// build/cleanDependencies.ts
export function cleanDependencies() {
const devDependencies = Object.keys(packageJSON.devDependencies || {});
const workspaceDependencies = Object.keys(
packageJSON.workspaces || {}
);

// Remove dev dependencies from production
for (const dep of devDependencies) {
if (!workspaceDependencies.includes(dep)) {
delete packageJSON.dependencies[dep];
}
}

// Keep only production dependencies
return packageJSON.dependencies;
}

This ensures production builds only include necessary dependencies.

Database Migration Reliability

Direct SQL Application

We rewrote the migration runner to apply SQL directly for better reliability:

// server/db/migrations/runner.ts
export async function runMigrations(db: Database) {
const migrationFiles = await getMigrationFiles();

for (const file of migrationFiles) {
const sql = await fs.readFile(file.path, 'utf-8');

try {
await db.exec(sql);
console.log(`Applied migration: ${file.name}`);
} catch (error) {
console.error(`Migration failed: ${file.name}`, error);
throw new MigrationError(
`Failed to apply migration: ${file.name}`,
error
);
}
}
}

Benefits:

  • Simpler execution model
  • Better error messages
  • More predictable behavior
  • Easier to debug

Migration Error Handling

We improved error handling with detailed context:

export class MigrationError extends Error {
constructor(
message: string,
public readonly migration: string,
public readonly cause: unknown
) {
super(message);
this.name = 'MigrationError';
}

toJSON() {
return {
name: this.name,
message: this.message,
migration: this.migration,
cause: this.cause
};
}
}

CI/CD Enhancements

Changeset Workflows

We added Changeset workflows for automated releases:

# .github/workflows/release.yml
name: Release
on:
push:
branches:
- main

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'

- run: pnpm install
- run: pnpm build
- run: pnpm changeset publish

Production Build Pipeline

We enhanced the production build pipeline with better caching:

# .github/workflows/build.yml
name: Build
on:
push:
branches: [develop, main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: docker/setup-buildx-action@v3

- name: Build Docker image
run: |
docker build \
--cache-from=type=gha \
--cache-to=type=gha,mode=max \
--target production \
-t molos-app:latest .

Key Commits

What's Next

The production build optimizations provide a solid foundation for scaling. Coming up:

  • Implementing build-time module validation
  • Adding layer caching to CI/CD pipelines
  • Enhancing Docker image scanning
  • Implementing blue-green deployment strategies
  • Adding performance monitoring and profiling tools

These build and Docker optimizations significantly improve deployment speed, reduce image sizes, and enhance the security of production deployments.