Back to Blog

From Monolith to Microservices: Migrating Applications with Convox

Modern applications often start as monoliths. A single codebase can be easier to develop, deploy, and maintain early on. But as your team grows and user demand increases, monoliths can become bottlenecks—slowing releases, making scaling inefficient, and increasing the blast radius of failures.

Microservices offer a solution: independent services that can scale, deploy, and evolve separately. The challenge is making the transition without disrupting your business. That's where Convox comes in. By providing a consistent deployment environment and simple configuration primitives, Convox helps teams gradually modernize legacy monoliths into microservices with minimal risk.


Why Migrate?

Benefits of microservices include:

  • Independent scaling for different workloads
  • Faster, more frequent deployments
  • Clearer boundaries between business domains
  • Reduced blast radius of failures
  • Better resource optimization with service-specific configurations

Challenges to prepare for:

  • More complex architecture and operations
  • Increased need for monitoring and observability
  • Potential latency in inter-service communication
  • Managing shared resources and data consistency

Convox provides guardrails—through the convox.yml manifest, built-in service discovery, environment management, automated rolling deployments, and health checks—that simplify these challenges and make incremental migration possible.


Getting Started with Convox

Before we dive into the migration, let's set up your Convox platform. Convox provides a powerful yet simple platform that runs in your own AWS, GCP, Azure, or DigitalOcean account.

Step 1: Sign Up for Free

Head to console.convox.com/signup and create your free account. We recommend using your company email address.

Step 2: Create a Runtime Integration

A Runtime Integration connects Convox to your cloud provider account, allowing it to provision and manage infrastructure on your behalf.

  1. In the Convox Console, click on Integrations in the left sidebar
  2. Click the + button in the Runtime section
  3. Select your cloud provider (AWS, GCP, Azure, or DigitalOcean)
  4. Follow the provider-specific instructions to grant Convox the necessary permissions
  5. Name your integration (e.g., "production-aws")

The Runtime Integration uses cloud-specific IAM roles and policies to securely manage resources in your account. You maintain full ownership and visibility of all infrastructure.

Step 3: Install Your First Rack

A Rack is a complete platform installation in your cloud account—think of it as your own private PaaS that includes everything needed to run containerized applications: a Kubernetes cluster, load balancers, SSL management, and more.

  1. Click on Racks in the Console
  2. Click Install Rack
  3. Select your Runtime Integration from Step 2
  4. Choose your region and configure settings (or use our recommended defaults)
  5. Click Install

Installation typically takes 10-20 minutes. While you wait, you can:

Step 4: Set Up CLI Access

  1. Install the Convox CLI following the instructions at docs.convox.com/installation/cli
  2. In the Console, go to your Account page and copy your CLI key
  3. Authenticate your CLI:
$ convox login console.convox.com -t <your-cli-key>

Once your Rack is running, you're ready to deploy your first application!


Phase 1: Containerize Your Monolith

If your monolith isn't already containerized, start there. Containerization is the foundation for your microservices journey, and it's simpler than you might think.

Creating Your First Dockerfile

A Dockerfile is a text file that contains instructions for building your container image. Here are examples for common monolith stacks:

Node.js Monolith Example:

FROM node:18-alpine

# Set working directory
WORKDIR /usr/src/app

# Copy dependency definitions
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application code
COPY . .

# Expose the port your app runs on
EXPOSE 3000

# Start the application
CMD ["node", "server.js"]

Ruby on Rails Monolith Example:

FROM ruby:3.1-alpine

# Install dependencies
RUN apk add --update --no-cache \
    build-base \
    postgresql-dev \
    nodejs \
    yarn

WORKDIR /app

# Install gems
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs 4

# Copy application
COPY . .

# Precompile assets (for Rails)
RUN bundle exec rails assets:precompile

EXPOSE 3000

CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

Python/Django Monolith Example:

FROM python:3.11-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

EXPOSE 8000

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Dockerization Best Practices for Monoliths

1. Start Simple: Don't try to optimize everything in your first Dockerfile. Get it working, then improve.

2. Handle Environment Variables: Your monolith likely has many configuration settings. Use environment variables:

# Don't hardcode values
ENV DATABASE_URL=${DATABASE_URL}
ENV API_KEY=${API_KEY}

3. Manage Secrets Properly: Never include secrets in your Dockerfile. Convox will inject them via environment variables:

# In your convox.yml
environment:
  - DATABASE_URL
  - API_KEY
  - SESSION_SECRET

4. Optimize Build Times: Use Docker's layer caching by copying dependency files first:

# Good - dependencies cached separately
COPY package.json package-lock.json ./
RUN npm install
COPY . .

# Bad - any code change invalidates npm install cache
COPY . .
RUN npm install

5. Consider Multi-Stage Builds for compiled languages or asset building:

# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Runtime stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

6. Test Locally First: Before deploying to Convox, test your container locally:

# Build the image
$ docker build -t myapp .

# Run it locally
$ docker run -p 3000:3000 myapp

# Test with environment variables
$ docker run -p 3000:3000 -e DATABASE_URL=postgres://localhost/myapp myapp

Common Gotchas When Dockerizing Monoliths

  • File Permissions: Ensure your app can write to necessary directories (logs, uploads, temp files)
  • Database Connections: Update connection strings to use environment variables
  • Asset Compilation: For frameworks like Rails, remember to compile assets during build
  • Health Checks: Ensure your app responds to health check endpoints
  • Port Binding: Bind to 0.0.0.0 not localhost inside containers

Once you have your Dockerfile ready, create your Convox application:

First, create your application in Convox:

$ convox apps create myapp -r myrack
Creating myapp... OK

Then create a minimal convox.yml:

services:
  web:
    build: .
    port: 3000
    health: /health
    scale:
      count: 2
      cpu: 512
      memory: 1024
resources:
  mydb:
    type: postgres
    options:
      storage: 100

Key points:

  • The web service is your entire monolith
  • The mydb resource provisions a PostgreSQL database - by default, this creates a containerized Postgres instance perfect for development and testing
  • Convox automatically injects connection details as environment variables (MYDB_URL, MYDB_HOST, etc.)
  • Health checks ensure your service is ready before receiving traffic

Database Options: From Development to Production

The containerized Postgres shown above is ideal for getting started quickly, but Convox also supports fully managed databases for production workloads:

When you're ready for production, simply change your resource type to use AWS RDS:

resources:
  mydb:
    type: rds-postgres  # Fully managed AWS RDS
    options:
      class: db.t3.medium
      storage: 100
      encrypted: true
      backupRetentionPeriod: 7
      deletionProtection: true

Convox supports both containerized and managed versions of:

The best part? Your application code doesn't change - Convox injects the same environment variables regardless of whether you're using containerized or managed resources. This lets you start cheap and simple, then scale up to managed services when needed.

At this stage, you haven't changed your application—just the way it's deployed. You now have a stable baseline for modernization with automated deployments, rolling updates, and built-in monitoring through convox logs.


Phase 2: Identify Service Boundaries

Look at your monolith and identify logical modules that could become microservices. Common starting points include:

  • Background jobs (e.g., scheduled tasks, data pipelines)
  • Notifications or email sending
  • Authentication or billing
  • File processing or media handling

Think in terms of bounded contexts—pieces of the app that can operate with minimal coupling. Use your application logs (convox logs -a myapp) to understand traffic patterns and dependencies.


Phase 3: Extract the First Microservice

Let's say notifications are the first target. You can split them into a new service while the monolith continues handling everything else.

Update your convox.yml:

services:
  web:
    build: .
    port: 3000
    health: /health
    environment:
      - NOTIFICATIONS_URL
    scale:
      count: 2-5
      targets:
        cpu: 70
  
  notifications:
    build: ./notifications
    port: 4000
    internal: true
    health: /health
    scale:
      count: 1-3
      targets:
        memory: 80

resources:
  mydb:
    type: postgres
    options:
      storage: 100
  
  queue:
    type: redis

Important features:

  • internal: true ensures the notifications service is only accessible within the rack
  • Automatic service discovery: the web service can reach notifications at http://notifications.myapp.convox.local:4000
  • Independent scaling with autoscaling targets for each service
  • Shared database access (both services can access mydb)
  • Added Redis queue for async communication between services

First create the notifications app if it's separate, or deploy the updated configuration:

$ convox deploy -a myapp

If something goes wrong, instant rollback is available:

$ convox releases rollback RBCDEFGHIJ -a myapp

Phase 4: Manage Data and Resources

One of the hardest parts of microservices migration is dealing with the database. Initially, multiple services may still talk to the same schema. That's okay in the early stages, but over time you'll want to assign ownership of data to specific services.

Convox resources make this flexible:

Development Environment

Use containerized databases for fast iterations:

resources:
  mydb:
    type: postgres
    options:
      storage: 20

Production Environment

Switch to managed AWS RDS for production with resource overlays:

$ convox env set MYDB_URL=postgres://user:pass@prod-db.amazonaws.com:5432/db -r production

This overrides the containerized resource with your production database without changing code.

Advanced: Service-Specific Databases

As your services mature, give each its own database:

services:
  web:
    resources:
      - webdb
  notifications:
    resources:
      - notificationdb

resources:
  webdb:
    type: rds-postgres
    options:
      class: db.t3.medium
      storage: 100
      encrypted: true
  
  notificationdb:
    type: rds-postgres
    options:
      class: db.t3.small
      storage: 50

Phase 5: Scale and Operate

Once you have multiple services, scaling becomes service-specific. A worker service processing jobs may need many replicas, while an API service might need fewer but with more CPU.

Manual Scaling

$ convox scale notifications --count 5 --cpu 256 --memory 512 -a myapp

Autoscaling

Define autoscaling in your convox.yml:

services:
  notifications:
    scale:
      count: 1-10
      targets:
        cpu: 70
        memory: 90

Custom Metrics with Datadog

For advanced autoscaling based on business metrics:

services:
  notifications:
    scale:
      count: 1-5
      targets:
        external:
          - name: "datadogmetric@default:notification-queue-depth"
            averageValue: 100

Observability

Convox provides comprehensive logging and monitoring. As you break apart your monolith, service-level logging becomes crucial:

# View all logs for your application
$ convox logs -a myapp

# Focus on specific service logs when debugging
$ convox logs -a myapp -s notifications

# View only web service logs with timestamps
$ convox logs -a myapp -s web --since 1h

# Follow logs in real-time for a specific service
$ convox logs -a myapp -s notifications --follow

# Filter logs for specific text patterns
$ convox logs -a myapp --filter error

# Real-time metrics (requires rack monitoring enabled)
$ convox rack params set monitoring=true -r myrack

This service-level log filtering is invaluable during migration—you can isolate issues to specific services without wading through the entire application's log stream.


Best Practices During Migration

Use the Strangler Fig Pattern

Route specific functionality to new services while the monolith continues to serve everything else. Use path-based routing:

services:
  web:
    domain: myapp.com
    port: 3000
  
  notifications:
    domain: myapp.com
    port: 4000
    # Route /api/notifications/* to this service

Start with Low-Risk Services

Begin with:

  • Scheduled jobs (using Convox timers)
  • Background processors
  • Read-only reporting services
  • Static asset serving
timers:
  cleanup:
    schedule: "0 3 * * *"
    command: bin/cleanup
    service: worker

Keep Services Internal First

Use internal: true and test thoroughly before exposing services externally. This provides a safety net during development.

Implement Health Checks

Every service should have proper health checks to ensure smooth deployments:

services:
  notifications:
    health:
      path: /health
      interval: 10
      timeout: 3
      grace: 30

Handle Service Communication

Use Convox's built-in service discovery for internal communication:

  • Internal services: http://servicename.appname.convox.local:port
  • External services: Available via the router URL from convox services

Automate Testing and CI/CD

Integrate Convox with your CI/CD pipeline:

# In your CI pipeline
$ convox test -a myapp
$ convox deploy -a myapp --wait

Be Mindful of Data Ownership

Plan for how you'll transition from a shared database to service-specific databases over time. Use database migrations carefully and consider event-driven architectures for data synchronization.


Advanced Migration Strategies

Blue-Green Deployments

Create parallel environments for zero-downtime migrations:

# Deploy new microservices architecture to staging
$ convox apps create myapp-new -r staging
$ convox deploy -a myapp-new

# Test thoroughly, then switch production
$ convox apps create myapp-new -r production
$ convox deploy -a myapp-new

# Update DNS to point to new app

Feature Flags

Gradually roll out microservices using feature flags in your application code, allowing you to toggle between monolith and microservice implementations.

Circuit Breakers

Implement circuit breakers in your services to handle failures gracefully when dependent services are unavailable.


How Convox Simplifies Migration

  • convox.yml defines all services, resources, timers, and configurations in one file
  • Service Discovery means services can talk to each other by name without custom networking
  • Resources make it easy to manage databases, caches, and queues across environments
  • Rolling Deployments & Instant Rollbacks ensure safe, reversible migrations
  • Environment Management (convox env) keeps configuration consistent across services
  • Health Checks prevent bad deployments from affecting users
  • Autoscaling handles varying loads automatically
  • Built-in SSL with automatic certificate management via Let's Encrypt
  • Container Registry management without additional setup

Convox provides the consistency needed to run both a legacy monolith and new microservices side by side, easing the complexity of hybrid architectures.


Real-World Example: E-Commerce Platform Migration

Consider an e-commerce monolith with these components:

  • Product catalog
  • Shopping cart
  • Order processing
  • Payment handling
  • Inventory management
  • Email notifications

Here's how you might progressively extract services:

Step 1: Extract Email Service

services:
  web:
    build: .
    port: 3000
    environment:
      - EMAIL_SERVICE_URL=http://email.ecommerce.convox.local:4000
  
  email:
    build: ./services/email
    port: 4000
    internal: true
    resources:
      - queue

Step 2: Extract Inventory as Read-Only Service

services:
  inventory:
    build: ./services/inventory
    port: 5000
    internal: true
    scale:
      count: 2-10
      targets:
        cpu: 60

Step 3: Separate Payment Processing

services:
  payments:
    build: ./services/payments
    port: 6000
    internal: true
    environment:
      - STRIPE_KEY
      - PAYMENT_ENCRYPTION_KEY
    health:
      path: /health
      timeout: 10
    scale:
      cpu: 1024
      memory: 2048

Each service can be developed, deployed, and scaled independently while maintaining the monolith's core functionality.


Monitoring Your Migration

Track your migration progress with Convox's built-in monitoring and alerting capabilities:

1. Enable Metrics Collection

In the Convox Console:

  • Navigate to your rack
  • Click Rack Settings in the left sidebar
  • Scroll to Dashboard Settings
  • Toggle Enable Metrics Agent to on
  • Wait approximately 2 minutes for the agents to install and begin collecting data

2. Use Pre-configured Dashboards

Once enabled, Convox automatically creates default panels that provide immediate visibility:

  • Rack CPU Usage - Monitor overall CPU utilization
  • Rack Memory Usage - Track memory consumption patterns
  • Application Pod Count - See running instances per service
  • Application Pod Ready % - Monitor service health percentages

These panels give you baseline metrics to track how your migration affects resource usage.

3. Create Custom Panels for Migration Tracking

Navigate to V3 Dashboard in the Metrics section to create panels specific to your migration:

Using Smart Queries (Recommended for ease):

  • Click Create Panel
  • Select your Rack
  • Choose specific apps or services to monitor
  • Select from Smart Query metrics like:
    • CPU Usage Trend (compare monolith vs microservices)
    • Memory Usage Trend (track resource efficiency gains)
    • Network I/O patterns (monitor inter-service communication)
    • Request rates (measure service-specific load)

Using Custom PromQL Queries (for advanced users):

  • Input any valid PromQL query to access the full range of collected metrics
  • Track custom business metrics or complex service interactions

4. Set Up Migration-Specific Alerts

Configure alerts in the Alert Manager to catch issues during migration:

  • Service response time degradation when splitting services
  • Memory spikes during data migration
  • CPU threshold breaches on newly extracted services
  • Failed health checks on new microservices

Example alert for monitoring a newly extracted service:

  • Rule Name: "Notification Service High Latency"
  • Panel: Select your service response time panel
  • Condition: Greater than 500ms
  • For Seconds: 60 (only alert if sustained)
  • Severity: Warning

5. Track Deployment Frequency

Monitor how often each service is deployed—more frequent deployments per service (compared to your monolith) indicate successful decomposition and team autonomy.


Conclusion

Migrating from a monolith to microservices doesn't have to be an all-or-nothing proposition. With Convox, you can start by containerizing your existing app, gradually peel off services, and run both architectures together during the transition.

By taking a phased approach—and leveraging Convox features like declarative manifests, automatic service discovery, managed resources, and rolling deployments—you reduce migration risk while modernizing your architecture at your own pace.

The journey from monolith to microservices is unique for every organization, but with Convox's consistent platform and powerful primitives, you have the tools to make it successful.


Start Your Microservices Journey Today

Breaking up a monolith is challenging, but it doesn't have to be overwhelming. Convox gives you the infrastructure automation and deployment simplicity you need to migrate confidently, without sacrificing control or adding operational overhead.

Get Started Free with Convox and see how straightforward microservices can be when your platform handles the complexity. Join thousands of teams who've successfully modernized their applications with Convox's battle-tested platform. Every new account includes free onboarding support to ensure you're productive from day one.

Your monolith has served you well, but your business is ready for more—faster deployments, better scalability, and increased resilience. With Convox, you get the power of microservices without the infrastructure headaches, all running securely in your own cloud account.


Ready to dive deeper? Follow our interactive onboarding guide to deploy your first service in minutes, explore our comprehensive documentation for detailed migration patterns and best practices, or reach out to our team to discuss your specific migration strategy. Whether you're extracting your first service or orchestrating dozens, Convox is here to make your microservices migration a success story—not a cautionary tale.

Let your team focus on what matters.