Devlog Mar 1: Production Build & Docker Optimizations
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
- feat(ci): implement automated release management system with Changesets
- chore: migrate runtime to bun and simplify container configuration
- refactor(deps): migrate production runtime from bun to nodejs
- build(docker): optimize module handling and database migration reliability
- build(docker): use dev image tag for development builds
- build(docker): optimize production build with bun runtime
- Install ca-certificates for https
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.
