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.