Building with AI – A Developer's Diary

A Local LAMP Stack with Docker and CodeIgniter

No MAMP. No global PHP install. Just Docker, a Dockerfile, and a clean development environment you can rebuild from scratch in five minutes.

NoCo Interactive • Docker · PHP · CodeIgniter · Apache • 8–10 minute read •

There is a particular kind of frustration that comes from your local development environment being more complicated than the project you are trying to build. You want to write a controller. Instead you spend an hour reading about PHP version conflicts, Apache configs that only work on one machine, or a MAMP setup that broke when you updated macOS. The tooling is supposed to disappear. When it does not, it is the only thing you can think about.

Docker fixes this—not by being simple, but by being consistent. Once you have a working setup, it works everywhere, every time, on any machine. This walkthrough builds a full local LAMP stack with CodeIgniter 4 from scratch. By the end you have a running server at http://localhost:8080, a MySQL database, and a workflow for starting the day that takes about ten seconds.

1. Create the project folder

Start with an empty directory. Everything—the app, the Dockerfile, the Compose file—lives here.

Shell
mkdir LAMP
cd LAMP

2. Install CodeIgniter

You do not need PHP installed locally. Docker has it. Use a throwaway Composer container to scaffold the CodeIgniter project into a subfolder called app.

Shell
docker run --rm -v "$PWD":/app -w /app composer:2 create-project codeigniter4/appstarter app

That creates the CodeIgniter skeleton, but it does not install the vendor dependencies inside it. Run a second pass to do that:

Shell
docker run --rm -v "$PWD/app":/app -w /app composer:2 install --ignore-platform-req=ext-intl

This step is easy to miss, and the error it produces is misleading. If you skip it, you will eventually see something like:

Shell
Failed opening required '/var/www/html/vendor/codeigniter4/framework/system/Boot.php'

The message sounds like CodeIgniter is missing. It is not—the framework is there, but its vendor/ folder is empty. Running composer install fills it in, and the error goes away.

3. Create the Dockerfile

In the LAMP folder—not inside app—create the Dockerfile. This defines the PHP and Apache environment your container will run.

Dockerfile
FROM php:8.4-apache

RUN apt-get update \
    && apt-get install -y libicu-dev zip unzip \
    && docker-php-ext-install intl mysqli pdo_mysql \
    && a2enmod rewrite

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

WORKDIR /var/www/html

Three things worth noting here. First, docker-php-ext-install intl handles the platform requirement that made you pass --ignore-platform-req in step 2—the container has it, your local machine does not. Second, a2enmod rewrite enables Apache's mod_rewrite, which CodeIgniter needs for clean URLs. Third, the Dockerfile copies an Apache virtual host config you have not created yet—that comes next.

4. Configure Apache

Create the directory and config file:

Shell
mkdir -p docker/apache

Then create docker/apache/default.conf:

Apache
<VirtualHost *:80>
    ServerName localhost
    DocumentRoot /var/www/html/public

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

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

The key line is DocumentRoot /var/www/html/public. CodeIgniter's entry point is public/index.php, not the project root. If you point Apache at the root, requests will hit the wrong place and you will get a blank page or a 403. Point it at public/ and everything routes correctly.

5. Create the Compose file

Create docker-compose.yml in the LAMP folder. This defines two services—the web server and a MySQL database—and wires them together.

YAML
services:
  web:
    build: .
    container_name: ci_web
    ports:
      - "8080:80"
    volumes:
      - ./app:/var/www/html
    depends_on:
      - db

  db:
    image: mysql:8.4
    container_name: ci_db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: codeigniter
      MYSQL_USER: ci
      MYSQL_PASSWORD: ci_secret
      MYSQL_ROOT_PASSWORD: root_secret
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

The volumes mount on the web service maps your local ./app folder into the container at /var/www/html. This is what makes live editing work—you change a file in your editor, reload the browser, the container sees it immediately. No rebuild required for code changes.

6. Configure CodeIgniter

CodeIgniter reads environment settings from a .env file. Create one from the template:

Shell
cd app
cp env .env

Open .env and set the following values. The database hostname is db—the name of the service in docker-compose.yml, not localhost. Docker's internal networking resolves service names automatically.

Shell
CI_ENVIRONMENT = development

app.baseURL = 'http://localhost:8080/'

database.default.hostname = db
database.default.database = codeigniter
database.default.username = ci
database.default.password = ci_secret
database.default.DBDriver = MySQLi
database.default.port = 3306

Then step back up to the project root:

Shell
cd ..

7. Start the server

Build the image and start the containers:

Shell
docker compose up -d --build

The first build takes a minute or two while Docker pulls the base image and installs PHP extensions. Subsequent starts are much faster. When it finishes, open a browser and go to:

Shell
http://localhost:8080

You should see the CodeIgniter welcome page. If you do, the stack is working—Apache is serving CodeIgniter from the correct document root, PHP has its extensions, and the database is up and waiting.

Coming back the next day

This is the part that makes Docker worth the setup cost. Tomorrow morning, you do not reinstall anything. You do not check PHP versions or restart MAMP or remember which port your local database uses. You just come back to the project folder and bring the containers up.

Shell
cd LAMP
docker compose up -d

That is it. The containers start in the background—usually in two or three seconds—and your environment is exactly as you left it. If you want to confirm CodeIgniter is awake before you start writing code, you can run:

Shell
docker compose exec web php spark --version

Then open your editor, change a controller, refresh the browser. The project is ready. Coffee is optional, but spiritually recommended.

The goal of a local development environment is to get out of the way. Docker earns its complexity by making the environment itself something you set up once, commit to version control, and never think about again.

Project structure at a glance

When everything is in place, your LAMP folder looks like this:

Shell
LAMP/
├── app/                     # CodeIgniter project
│   ├── .env
│   ├── vendor/
│   └── public/
│       └── index.php
├── docker/
│   └── apache/
│       └── default.conf
├── Dockerfile
└── docker-compose.yml

The app/ folder is your working directory. Everything else is infrastructure—the files that build and configure the containers. You mostly ignore them once they're working.

Stopping the server: docker compose down stops and removes the containers. Your code and database data are preserved—code in ./app, database in the db_data Docker volume. Run docker compose up -d to bring it back.

← Back to Building with AI – A Developer's Diary