Full LAMP stack in docker

Scenario: Customer want to have entire LAMP stack in docker for better scalability, simple management, updates, etc.

Solution: This will install standard LAMP stack + phpMyAdmin + HTTPS with Let`s encrypt. (this specific configuration created for OJS 3.5. Modify it for your needs)

Solution directory structure:

lamp-docker/
│
├── docker-compose.yml
├── letsecrypt/
│   └── acme.json
└── php/
    └── Dockerfile
    └── vhost.conf

Docker-compose content:

networks:
  lampnet:
    driver: bridge

services:
  traefik:
    image: traefik:3.6
    container_name: traefik
    restart: always
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@your-domain.ca"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--log.level=INFO"
      - "--accesslog=true"
      - "--accesslog.filepath=/var/log/traefik/access.log"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
      - ./logs/traefik:/var/log
    networks:
      - lampnet

  apache-php:
    build: ./php
    container_name: apache-php
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.ojs.rule=Host(`your-domain.ca`)"
      - "traefik.http.routers.ojs.entrypoints=websecure"
      - "traefik.http.routers.ojs.tls.certresolver=letsencrypt"
      - "traefik.http.services.ojs.loadbalancer.server.port=80"
      - "traefik.http.middlewares.ojs-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
      - "traefik.http.middlewares.ojs-headers.headers.customrequestheaders.X-Forwarded-Host=your-domain.ca"
      - "traefik.http.routers.ojs.middlewares=ojs-headers"
    volumes:
      - ./www:/var/www/html
      - ./logs/apache:/var/log/apache2
    networks:
      - lampnet

  mariadb:
    image: mariadb:11
    container_name: mariadb
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root_superpassword
      MYSQL_DATABASE: ojsdb
      MYSQL_USER: ojsuser
      MYSQL_PASSWORD: superpassword
    command: --bind-address=0.0.0.0
    ports:
      - "127.0.0.1:3306:3306"
    volumes:
      - ./mariadb_data:/var/lib/mysql
    networks:
      - lampnet

  phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    container_name: phpmyadmin
    restart: always
    environment:
      PMA_HOST: mariadb
      PMA_PORT: 3306
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pma.rule=Host(`your-domain.ca`) && PathPrefix(`/myadmin`)"
      - "traefik.http.routers.pma.entrypoints=websecure"
      - "traefik.http.routers.pma.tls.certresolver=letsencrypt"
      - "traefik.http.middlewares.pma-add-slash.redirectregex.regex=^(.*/myadmin)$"
      - "traefik.http.middlewares.pma-add-slash.redirectregex.replacement=$1/"
      - "traefik.http.middlewares.pma-add-slash.redirectregex.permanent=true"
      - "traefik.http.middlewares.pma-stripprefix.stripprefix.prefixes=/myadmin"
      - "traefik.http.routers.pma.middlewares=pma-add-slash,pma-stripprefix"
      - "traefik.http.services.pma.loadbalancer.server.port=80"
    volumes:
      - ./logs/phpmyadmin:/var/log
    networks:
      - lampnet

Content of php/Dockerfile:

FROM php:8.3-apache

RUN apt-get update && apt-get install -y \
    libicu-dev \
    libxml2-dev \
    libzip-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    libonig-dev \
    libbz2-dev \
    unzip \
    ftp \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install intl mbstring xml bcmath zip gd ftp pdo_mysql mysqli\
    && a2enmod rewrite \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*


# Virtual host
COPY vhost.conf /etc/apache2/sites-available/000-default.conf
COPY custom-php.ini /usr/local/etc/php/conf.d/

# Set Apache log location to host-mounted volume
RUN mkdir -p /var/log/apache2
RUN chown -R www-data:www-data /var/www/html

content of php/vhost.conf (Apache virtual host).

<VirtualHost *:80>
    ServerName your-domain.ca
    DocumentRoot /var/www/html
    ErrorLog /var/log/apache2/your-domain-error.log
    CustomLog /var/log/apache2/your-domain-access.log combined

    <Directory /var/www/html>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

content of php/custom-php.ini

memory_limit = 256M
upload_max_filesize = 50M
post_max_size = 50M
max_execution_time = 120

how to connect to DB from host:

install mysql-client on host. 
use following connand to connect:
mysql --protocol=TCP -h 127.0.0.1 -P 3306 -u ojsuser -p

Script for operations. control.sh

#!/bin/bash

set -e

COMMAND=$1

show_help() {
    echo "Usage: $0 {start|stop|build|restart|rebuild|status}"
    echo
    echo "  start     - Start all containers"
    echo "  stop      - Stop all containers"
    echo "  build     - Build local container images"
    echo "  restart   - Stop then start containers"
    echo "  rebuild   - Stop → rebuild images → start"
    echo "  status    - Show running service list"
    exit 1
}

if [ -z "$COMMAND" ]; then
    show_help
fi

case "$COMMAND" in

    start)
        echo "---- Starting containers ----"
        docker compose up -d
        echo "---- Done ----"
        ;;

    stop)
        echo "---- Stopping containers ----"
        docker compose down
        echo "---- Done ----"
        ;;

    build)
        echo "---- Building container images ----"
        docker compose build
        echo "---- Done ----"
        ;;

    restart)
        echo "---- Restarting containers ----"
        docker compose down
        docker compose up -d
        echo "---- Done ----"
        ;;

    rebuild)
        echo "---- Stopping containers ----"
        docker compose down
        echo "---- Rebuilding images ----"
        docker compose build --no-cache
        echo "---- Starting containers ----"
        docker compose up -d
        echo "---- Done ----"
        ;;

    status)
        echo "---- Container status ----"
        docker compose ps
        ;;

    *)
        echo "Error: Unknown command '$COMMAND'"
        show_help
        ;;
esac

Start containers with script. Build process depends on your system and internet connection. In my case it took around 5 min to build containers and start services.

Done.