Infrastructure 5 min read

Containerising Legacy Applications: A Pragmatic Approach with Docker

How to isolate, stabilise, and modernise legacy web services using minimal Docker Compose setups without rewriting existing codebases from scratch.

Many businesses depend on stable, revenue-generating web applications that were written 5 to 10 years ago. These systems work well, but they often run on an ageing virtual private server (VPS) running an unsupported Linux distribution, where no one dares to touch the environment for fear of breaking fragile runtime dependencies.

Rewriting the entire application is usually cost-prohibitive and introduces massive risk.

A far more pragmatic first step is containerising the existing application with Docker. Here is our step-by-step strategy for doing so cleanly.

The Goal: Reproducibility and Isolation

The goal of containerising legacy applications is not to turn them into microservices. The goal is to pin down runtime dependencies (PHP/Ruby/Node versions, system libraries, configuration files) so the application can run anywhere reliably.

┌──────────────────────────────────────────────┐
│  Host OS (Modern Linux: Debian/Ubuntu LTS)   │
│                                              │
│   ┌──────────────────────────────────────┐   │
│   │ Reverse Proxy (Nginx + Let's Encrypt)│   │
│   └──────────────────┬───────────────────┘   │
│                      │                       │
│        Docker Internal Network               │
│                      │                       │
│   ┌──────────────────▼───────────────────┐   │
│   │ Legacy App Container                 │   │
│   │ (Exact legacy runtime & dependencies)│   │
│   └──────────────────┬───────────────────┘   │
│                      │                       │
│   ┌──────────────────▼───────────────────┐   │
│   │ Dedicated Database Container         │   │
│   │ (Isolated storage volume)            │   │
│   └──────────────────────────────────────┘   │
└──────────────────────────────────────────────┘

Step 1: Pin Exact Legacy Runtimes

Rather than upgrading language runtimes immediately, match the current production version inside a Dockerfile.

For example, if your legacy service runs on an older PHP 7.4 stack:

FROM php:7.4-fpm-alpine

# Install exact legacy dependencies
RUN apk add --no-cache libpng-dev libzip-dev \
    && docker-php-ext-install pdo_mysql gd zip

WORKDIR /var/www/html
COPY . .

# Enforce secure file permissions
RUN chown -R www-data:www-data /var/www/html

By freezing the exact language and library version in the container, you eliminate “works on my machine” issues and ensure the application boots identically on any modern host.

Step 2: Extract Configuration to Environment Variables

Legacy apps frequently hardcode database credentials and API keys in configuration files like config.php or settings.py.

Refactor these files to read from system environment variables:

// Before: Hardcoded credentials in source control
$dbHost = '127.0.0.1';
$dbUser = 'root';
$dbPass = 'secret123';

// After: Environment variables with safe fallbacks
$dbHost = getenv('DB_HOST') ?: 'database';
$dbUser = getenv('DB_USER') ?: 'app_user';
$dbPass = getenv('DB_PASSWORD') ?: '';

This allows you to store sensitive credentials safely in a .env file on the host machine rather than checking them into version control.

Step 3: Decouple Persistent Storage

Containers must be disposable. If a container crashes or restarts, user uploads and session state should never be lost.

Map all directories where files are written to explicit Docker volumes:

services:
  app:
    build: .
    volumes:
      - uploads_data:/var/www/html/public/uploads
    environment:
      - DB_HOST=db
      - DB_NAME=app_production

  db:
    image: mariadb:10.5
    volumes:
      - db_data:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=app_production
      - MYSQL_USER=app_user

volumes:
  uploads_data:
  db_data:

Now, updating the application image can be done without touching the database or uploaded files.

Summary

Containerisation buys legacy applications a new lease on life. You get:

  • Rapid disaster recovery (re-spawning on a new host takes minutes instead of days).
  • Automated backup routines targeting isolated storage volumes.
  • A safe foundation from which you can incrementally modernise code components over time.

Written by Koster CX

Independent technical consultancy offering web hosting, modern web design, and custom software and Salesforce solutions.

Get in touch

Continue Reading

Related Articles

View all articles →