Why Use Docker for CodeIgniter 4 Development?

Docker simplifies dependency management by packaging your application and its entire runtime environment into lightweight, portable containers. For CodeIgniter 4 developers, this means you no longer need to worry about PHP version mismatches, missing extensions, or server configuration differences between your local machine, staging, and production. Docker Compose takes this further by letting you orchestrate multiple containers — such as a web server, a database, and optional tools like Adminer — using a single YAML configuration file.

Here is why Docker is a strong choice for CI4 projects:

  • Consistency: Every team member runs the exact same environment, eliminating "it works on my machine" problems.
  • Scalability: You can easily add services like Redis for caching or Mailhog for email testing without touching your host OS.
  • Isolation: Each project has its own containers, so dependency conflicts between projects are impossible.
  • Portability: Deploy to AWS, GCP, Azure, or any Docker-compatible host with minimal configuration changes.
  • Fast Onboarding: New developers can run docker compose up and have a working environment in minutes, not hours.

Compared to traditional setups like XAMPP, WAMP, or manual LAMP stacks, Docker provides a reproducible build process that you can version-control alongside your application code. This makes your infrastructure as transparent and reviewable as your PHP code.



How to Run CodeIgniter 4 with Docker and Docker Compose: Complete Tutorial with Code

Table Of Content

1 Prerequisites

Before diving in, make sure you have the following tools and knowledge ready:

  • Docker installed (version 20+ recommended). Download Docker here.
  • Docker Compose (usually bundled with Docker Desktop on Windows/Mac; on Linux, install via the docker-compose-plugin package).
  • Basic command-line/terminal knowledge (navigating directories, running commands).
  • Familiarity with PHP and basic understanding of how web servers like Apache work.
  • A code editor such as VS Code, PhpStorm, or Sublime Text.

If you are new to CodeIgniter 4, consider reading our guide to building a simple blog in CI4 first to understand the framework basics before containerizing it.

2 Introduction

Running CodeIgniter 4 directly on local machines often leads to frustrating environment issues — PHP version mismatches, missing extensions like intl or pdo_mysql, or inconsistent Apache configurations between development and production. These problems multiply when working in a team where each developer has a different OS and PHP setup.


Docker solves these problems by wrapping your entire application stack — PHP, Apache, MySQL, and all required extensions — into containers that behave identically on every machine. Combined with Docker Compose, you can define your full stack in a single docker-compose.yml file and spin everything up with one command.


In this guide, you will learn how to:

  • Create a custom PHP 8.1 + Apache Docker image with all CI4-required extensions.
  • Set up a MySQL 8.0 container with persistent data storage.
  • Configure environment variables, volumes, and networking between containers.
  • Install CodeIgniter 4 via Composer inside the container.
  • Connect your CI4 application to the MySQL database.

By the end of this tutorial, you will have a fully working CodeIgniter 4 application running inside Docker containers — ready for development and easily adaptable for production deployment.

3 Install Docker

On Ubuntu/Debian Linux, install Docker with these commands:

    
    sudo apt update
    sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
    

On Windows or macOS, download and install Docker Desktop, which includes both Docker Engine and Docker Compose out of the box.

After installation, verify everything is working:

    
    docker --version
    docker compose version
    

You should see version numbers for both commands. If docker compose is not recognized, ensure the Compose plugin is installed correctly.

4 Setting Up the Project Structure

Start by creating a project directory, say ci4-docker. Inside it, organize your files as follows:

    
        ci4-docker/
        ├── .env
        ├── .dockerignore
        ├── docker-compose.yml
        ├── docker/
        │   ├── apache/
        │   │   ├── Dockerfile
        │   │   └── 000-default.conf
        │   └── mysql/
        │       └── Dockerfile
        ├── src/  (this will hold your CodeIgniter app)
        ├── db_data/  (auto-created for MySQL persistence)
        └── apache_log/  (auto-created for logs)
    

This structure cleanly separates concerns: docker/ contains build configuration, src/ holds your application code, and the volume directories (db_data/, apache_log/) are auto-created by Docker for data persistence.

Important: Create a .dockerignore file in the project root to prevent unnecessary files from being sent to the Docker build context:

    
        db_data/
        apache_log/
        .git
        .env
        README.md
    

This speeds up builds and keeps your images lean.

5 Configuring Environment Variables (.env)

Create a .env file in the root:

    
        MYSQL_ROOT_PASSWORD=your_secure_password
        MYSQL_DATABASE=ci4_db
        UID=1000  # Replace with your user ID (run `id -u` in terminal)
    
  • MYSQL_ROOT_PASSWORD secures your database.
  • MYSQL_DATABASE names your DB.
  • UID ensures file permissions match between host and container, preventing ownership errors when editing files locally.

Docker Compose will load these automatically.

6 Creating the Docker Compose File (docker-compose.yml)

This is the heart of your Docker setup. It defines three services: a MySQL database, an Apache/PHP web server, and Adminer for database management.

    
        services:
            db:
                build:
                    context: .
                    dockerfile: docker/mysql/Dockerfile
                environment: 
                    MYSQL_DATABASE: ${MYSQL_DATABASE}
                    MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
                command: --default-authentication-plugin=mysql_native_password
                restart: unless-stopped
                volumes:
                    - ./db_data:/var/lib/mysql
                ports:
                    - 3306:3306

            web:
                build:
                    context: .
                    dockerfile: docker/apache/Dockerfile
                    args:
                        uid: ${UID}
                environment:
                    - APACHE_RUN_USER=#${UID}
                    - APACHE_RUN_GROUP=#${UID}
                restart: unless-stopped
                volumes: 
                    - ./src:/var/www/html
                    - ./apache_log:/var/log/apache2
                ports:
                    - 80:80
                depends_on: 
                    - db

            adminer:
                image: adminer:latest
                restart: unless-stopped
                ports:
                    - 8080:8080
                depends_on:
                    - db
    

Explanation of each service:

  • db service: Builds from a custom MySQL Dockerfile, uses environment variables from your .env file for automatic database creation, persists data in ./db_data, and exposes port 3306 for external tools.
  • web service: Builds from the Apache/PHP Dockerfile, mounts your app code (./src) and logs (./apache_log) as volumes, and depends on the DB container for startup order.
  • adminer service: A lightweight database management UI. Access it at http://localhost:8080 to browse and manage your MySQL database visually.
  • volumes: Ensure that database data and logs survive container restarts and rebuilds.
  • ports: Maps container ports to your host (e.g., access the app at http://localhost and Adminer at port 8080).
  • depends_on: Tells Docker Compose to start the DB container before the web and adminer containers.
  • restart: unless-stopped: Automatically restarts containers after a crash or system reboot, unless you explicitly stop them.

Note: The top-level version key (e.g., version: '3') is deprecated in modern Docker Compose (v2+) and is no longer required. All services are automatically connected through a default Docker network, so the legacy links directive is also unnecessary.

Run docker compose up -d to start all services in detached mode.

7 Building the MySQL Container (docker/mysql/Dockerfile)

    
        FROM mysql:8.0
    

Explanation:

  • This Dockerfile is intentionally minimal — it simply pulls the official MySQL 8.0 image.
  • The environment variables MYSQL_ROOT_PASSWORD and MYSQL_DATABASE are passed via the environment block in docker-compose.yml, not inside the Dockerfile. Dockerfile ENV instructions do not interpolate Docker Compose variables — the Compose environment section is the correct place for runtime configuration.
  • The command override in Compose sets --default-authentication-plugin=mysql_native_password for compatibility with PHP's MySQLi and PDO drivers.
  • If you do not need a custom Dockerfile for MySQL, you can replace the build block with image: mysql:8.0 directly in your Compose file.

8 Building the Apache/PHP Container (docker/apache/Dockerfile)

This is more involved, as it installs PHP extensions and Composer.

    
      FROM php:8.1-apache

        RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf

        RUN apt-get update && apt-get install -y \
            git \
            zip \
            curl \
            sudo \
            unzip \
            libicu-dev \
            libbz2-dev \
            libpng-dev \
            libjpeg-dev \
            libmcrypt-dev \
            libreadline-dev \
            libfreetype6-dev \
            g++

        RUN docker-php-ext-install \
            bz2 \
            intl \
            bcmath \
            opcache \
            calendar \
            pdo_mysql \
            mysqli \
            zip \
            gd

        COPY docker/apache/000-default.conf /etc/apache2/sites-available/000-default.conf

        RUN a2enmod rewrite headers

        RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"

        RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

        ARG uid
        RUN useradd -G www-data,root -u $uid -d /home/devuser devuser
        RUN mkdir -p /home/devuser/.composer && \
            chown -R devuser:devuser /home/devuser

        WORKDIR /var/www/html

        EXPOSE 80
    

Explanation

  • Base Image: PHP 8.1 with Apache (update version if needed for CI4 compatibility).
  • APT Installs: Adds tools and libs for PHP extensions.
  • Extensions: Installs essentials like pdo_mysql for DB, gd for images, intl for internationalization.
  • Apache Config: Copies virtual host file, enables rewrite for CI4 routing.
  • Composer: Installs globally for dependency management.
  • User Setup: Creates a user matching host UID to fix permissions.
  • WORKDIR: Sets default directory to your app root.

9 Apache Virtual Host Config (docker/apache/000-default.conf)

    
        <VirtualHost *:80>
            ServerAdmin webmaster@localhost
            DocumentRoot /var/www/html/public

            <Directory /var/www/html>
                Options Indexes FollowSymLinks
                AllowOverride All
                Require all granted
            </Directory>

            ErrorLog ${APACHE_LOG_DIR}/error.log
            CustomLog ${APACHE_LOG_DIR}/access.log combined
        </VirtualHost>
    

Explanation

  • Points DocumentRoot to CI4's public/ folder.
  • Enables .htaccess overrides for clean URLs.
  • Logs to mounted volume for easy access.

10 Installing CodeIgniter 4

With containers running (docker compose up -d), open a shell inside the web container:

    
       docker compose exec web bash
    

Inside the container, install CodeIgniter 4 using Composer:

    
       # If /var/www/html is empty (first run):
       composer create-project codeigniter4/appstarter . --no-dev

       # Set correct ownership for Apache
       chown -R www-data:www-data /var/www/html/writable
       chmod -R 775 /var/www/html/writable
    

Explanation:

  • The . (dot) tells Composer to install into the current directory (/var/www/html), which is the mounted src/ volume. This avoids path conflicts.
  • --no-dev skips development dependencies for a leaner production-like install.
  • Setting ownership on the writable/ directory ensures Apache can write to cache, session, and log folders.

Troubleshooting: If you see "Project directory is not empty", make sure your src/ folder on the host is completely empty before running the install command. You can also delete hidden files like .gitkeep that might exist in the directory.

Exit the container with exit and restart the web service:

    
       docker compose restart web
    

Visit http://localhost — you should see the CodeIgniter 4 welcome page. If you see a blank page or 500 error, check apache_log/error.log on your host for details.

11 Connecting to the Database

Edit your CodeIgniter database configuration. Open src/app/Config/Database.php (or better yet, use src/.env) and set the connection details:

Option 1: Using Database.php directly

    
       public array $default = [
            'DSN'      => '',
            'hostname' => 'db',
            'username' => 'root',
            'password' => 'your_secure_password',
            'database' => 'ci4_db',
            'DBDriver' => 'MySQLi',
            'DBPrefix' => '',
            'pConnect' => false,
            'DBDebug'  => true,
            'charset'  => 'utf8mb4',
            'DBCollat' => 'utf8mb4_general_ci',
            'port'     => 3306,
        ];
    

Option 2: Using the .env file (Recommended)

    
       database.default.hostname = db
       database.default.database = ci4_db
       database.default.username = root
       database.default.password = your_secure_password
       database.default.DBDriver = MySQLi
       database.default.port = 3306
    

Key points:

  • The hostname db matches the service name in your docker-compose.yml. Docker's internal DNS automatically resolves this to the MySQL container's IP address.
  • Using the .env file approach is recommended because it keeps credentials out of version-controlled code.
  • Make sure the password matches the MYSQL_ROOT_PASSWORD you set in the root .env file.

To verify the connection, you can use Adminer at http://localhost:8080. Log in with server db, username root, and your password. You should see the ci4_db database listed.

You can also test the connection by running a CI4 migration. If you have set up a migration file, use:

    
       docker compose exec web php spark migrate
    

If you found this Docker guide useful, here are more CodeIgniter 4 tutorials from our site that pair well with a containerized setup:

12 Conclusion

Running CodeIgniter 4 with Docker and Docker Compose gives you a clean, reproducible, and production-ready development environment. By containerizing PHP, Apache, and MySQL together, you eliminate environment inconsistencies, simplify team onboarding, and make deployment to cloud platforms straightforward.


In this tutorial, you learned how to structure a Docker project for CI4, write custom Dockerfiles for both Apache/PHP and MySQL, configure environment variables, set up volumes for data persistence, and connect your CodeIgniter application to a containerized database.


Suggested next steps to level up your CI4 Docker setup:


Once your Docker setup is in place, you can focus entirely on building features instead of wrestling with server configurations. Happy coding!

Revathi M - PHP and CodeIgniter Developer

Written by Revathi M

PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel

Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.

Frequently Asked Questions

Change the base image in docker/apache/Dockerfile from php:8.1-apache to your desired version (e.g., php:8.3-apache). Then rebuild the container with: docker compose build web && docker compose up -d. Make sure to verify that your CodeIgniter 4 version supports the new PHP version.

Yes, you can replace the Apache-based PHP image with php:8.1-fpm and add a separate Nginx container. You will need to create an Nginx configuration file that proxies PHP requests to the FPM container. The docker-compose.yml would then have three services: db, php-fpm, and nginx.

You need Docker (version 20+) and Docker Compose installed on your system. On Windows and macOS, Docker Desktop includes both. Basic familiarity with PHP, MySQL, and terminal commands is helpful but not required — this tutorial explains every step.

Docker provides a consistent, isolated environment that works the same across development, testing, and production. It eliminates common issues like PHP version mismatches or missing extensions, and lets your entire team run an identical setup with a single command.

After starting your containers with docker compose up -d, open a shell inside the web container using: docker compose exec web bash. Then run: composer create-project codeigniter4/appstarter . --no-dev. This installs CI4 into the mounted volume. Set writable folder permissions with: chown -R www-data:www-data /var/www/html/writable.

Yes — this tutorial includes an Adminer service in the docker-compose.yml file. Once your containers are running, access Adminer at http://localhost:8080 in your browser. Log in with server "db", username "root", and your MySQL root password to manage your database visually.

Run the command "id -u" on your host machine to find your user ID, then set UID to that value in your project .env file. This ensures files created inside the container have the correct ownership on your host system, preventing "permission denied" errors when editing files locally.