Monolith to Microservices migration SRE Case Study: Modernizing a 10-Year-Old Python 2.7 Monolith on Flatcar Container Linux
how a media company containerized a legacy Python 2.7 monolith and deployed it on Flatcar Container Linux
Introduction
A major media streaming company found itself at a crossroads with one of its core services—a 10-year-old Python 2.7 monolith responsible for content recommendations. Over the years, this monolith had grown in complexity, with libraries added ad-hoc and dependencies living in conflict. The team constantly fought drift across different machines, struggled to maintain patch levels, and faced occasional production incidents triggered by inconsistent library versions. With a looming end-of-life for Python 2.7 and a broader company initiative to adopt containers, they decided it was time to modernize. Their journey took them from a sprawling monolith on traditional Linux servers to a containerized environment running on Flatcar Container Linux. This case study examines the migration steps, key benefits, and lessons learned.
Additonal Reference Reading: https://deploy.equinix.com/customers/flatcar-container-linux/
Background
When the media company launched its streaming platform ten years ago, Python 2.7 was a reliable choice—rich libraries, lots of community support, and straightforward deployment scripts. However, as the codebase expanded, development teams added more libraries, pinned dependencies at various versions, and introduced new APIs. Different servers ended up with slightly different environments, meaning deployment consistency became a constant challenge. Coupled with the performance overhead of manually patching each server, the team realized they needed a way to standardize.
Enter containers: While containerization was the organization’s goal, some of the development teams were still unsure about rewriting or re-architecting the entire codebase for Python 3. Instead, they saw the immediate benefits of containerizing the monolith in its current Python 2.7 state, then gradually refactoring inside a container. The ultimate plan was to break the monolith into smaller microservices—but the first step was a direct “lift and shift” into a Docker-based environment.
Goals
Consistent Environments: By bundling all the project’s libraries and Python 2.7 runtime in a Docker image, the monolith would run identically in dev, staging, and production.
Reduced Operational Overhead: Container images would eliminate the guesswork of whether each server had the correct versions of dependencies or OS patches.
Simplified Patch Cycles: Combining containers with a lightweight, immutable host OS—Flatcar Container Linux—would pave the way for automatic updates without risking library drift.
Approach
1. Containerizing the Python 2.7 Monolith
The development team created a Dockerfile:
FROM python:2.7-slim
# Install system-level dependencies
RUN apt-get update && apt-get install -y build-essential libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy application code
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
# Expose service port and define entrypoint
EXPOSE 8080
CMD ["python", "main.py"]
This Dockerfile consolidated all the old dependencies and pinned them in requirements.txt. Testing on a local dev machine, they confirmed the monolith functioned precisely as before, with no library conflicts or missing dependencies.
2. Testing Locally
They pulled the image into a local Docker environment, replicating production environment variables. The once cumbersome monolith ran smoothly, and the dev team caught a few environment-specific bugs early. This local environment—backed by Docker Compose—let them simulate a minimal version of other services, such as caching and database connections. It brought confidence that the container would behave consistently on every server.
3. Deploying on Flatcar Container Linux
The operations team selected Flatcar Container Linux for its minimal footprint and immutable design. By removing package managers and other unused services from the host OS, Flatcar helps reduce the attack surface and ensures automatic atomic updates. They used Terraform to provision new Flatcar instances with an Ignition configuration:
resource "aws_instance" "flatcar_ec2" {
ami = data.aws_ami.flatcar.id
instance_type = "t3.medium"
user_data = file("${path.module}/ignition.json")
...
}
That ignition.json included a systemd unit that pulls the Docker image and runs the monolith:
[Unit]
Description=Python 2.7 Monolith
After=network-online.target
Wants=network-online.target
[Service]
ExecStartPre=/usr/bin/docker pull myregistry/py27-monolith:latest
ExecStart=/usr/bin/docker run --rm \
-p 8080:8080 \
--name py27-monolith myregistry/py27-monolith:latest
Restart=always
[Install]
WantedBy=multi-user.target
With this approach, the entire deployment process was controlled and repeatable: Terraform launched identical Flatcar hosts, each with the same container definition. The dev team no longer worried about OS-level differences or library mismatches.
4. Gradual Refactoring
Once live in production, the team began incrementally refactoring the monolith into separate modules and libraries, each contained within the same repository but isolated within the Docker container. Over time, they plan to shift to Python 3 for each piece of functionality.
Results
Consistency: The monolith performed identically across dev, staging, and production. They saw a marked reduction in environment-related tickets and on-call incidents.
Stability: Flatcar’s atomic updates meant the OS automatically patched itself. The team configured rolling updates so that one host at a time restarted, ensuring minimal downtime. Each host always had a fresh, consistent OS image with Docker installed, eliminating patch drift.
Enhanced Security: By removing extraneous packages at both the container and host OS levels, the attack surface shrank. The team integrated daily vulnerability scans on the Docker image, quickly addressing any library CVEs discovered.
Roadmap to Microservices: Although still a monolith, the container environment provided a stepping stone. The devs could comfortably break out functionalities into microservices, each referencing a Docker base image with Python 3 or a more modern framework. The speed of iteration increased, thanks to a standardized environment.
Lessons Learned
Lift-and-Shift First, Refactor Later: Immediately rewriting a 10-year-old codebase for Python 3 or a microservice architecture would have introduced risk. Containerizing first stabilized the environment.
Automated OS Updates: Flatcar Container Linux alleviated the overhead of monthly patch schedules—updates simply arrived and rolled out with minimal production impact.
Local Testing Reaps Rewards: Creating a local environment that mirrored production saved significant debugging time. The dev team caught subtle library version issues early.
Gradual Evolution: Even older technologies (Python 2.7) can live in a modern container workflow, buying time for eventual migrations without halting business value.
Conclusion
Through containerizing a ten-year-old Python 2.7 monolith, the media streaming company successfully modernized its deployment pipeline and improved reliability without undertaking a risky full rewrite. Running containers on Flatcar Container Linux provided an immutable, self-updating host OS that further reduced operational toil. While the monolith still exists, the container-based approach enables a smooth path toward microservices and Python 3 adoption, ensuring the company’s infrastructure remains future-proof.

