CodeIgniter is the lightweight PHP framework people reach for when they want MVC structure without the ceremony of Symfony or Laravel. It's fast, has a tiny footprint, and its docs are excellent. But "lightweight" can lull you into deploying it the wrong way — copying files over FTP, leaving CI_ENVIRONMENT on development, and exposing the whole project root. This guide covers a clean container deployment of CodeIgniter 4.
Why containerize CodeIgniter
CodeIgniter 4 has a clear separation between the framework, your app, and the public web root. In production you want:
- Only
public/exposed as the document root. CI_ENVIRONMENT=productionso detailed error pages are suppressed.php-fpm+ nginx (or FrankenPHP) instead ofphp spark serve, which is a dev-only server.- Dependencies installed with Composer, not committed
vendor/.
The directory layout
my-ci-app/
├── app/
├── public/
│ └── index.php
├── system/
├── writable/
├── composer.json
├── env
└── sparkThe public/ directory is your document root. Everything else — including app/ and the env file — must stay outside the web-accessible path. Pointing your server at the project root instead of public/ is the most common CodeIgniter security mistake.
Production Dockerfile
FROM php:8.3-fpm-alpine AS app
RUN apk add --no-cache nginx \
&& docker-php-ext-install pdo_mysql opcache intl
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
COPY . .
# nginx config points root at /var/www/html/public
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
COPY docker/start.sh /start.sh
RUN chmod +x /start.sh && chmod -R 775 writable
ENV CI_ENVIRONMENT=production
EXPOSE 8080
CMD ["/start.sh"]The nginx config is short but matters:
server {
listen 8080;
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}The start.sh script launches php-fpm and nginx together:
#!/bin/sh
php-fpm -D
nginx -g 'daemon off;'Configuration via environment variables
CodeIgniter reads from an env file, but in production you should set real environment variables instead. The framework maps dotted config keys to env vars. Key ones:
| Env var | Purpose |
|---|---|
CI_ENVIRONMENT | production — disables debug, hides errors |
app.baseURL | Your public URL, e.g. https://app.example.com/ |
app.indexPage | Empty string to drop index.php from URLs |
database.default.hostname | DB host |
database.default.database | DB name |
database.default.username / .password | Credentials |
database.default.DBDriver | MySQLi or Postgre |
encryption.key | Required for sessions/encryption |
Set app.baseURL correctly — if it's wrong, asset URLs and redirects break. Behind an HTTPS-terminating ingress, set it to your https:// domain explicitly because CodeIgniter won't infer the scheme reliably from forwarded headers.
Wiring up the database
With a managed MySQL 8 instance, your app gets connection details injected. CodeIgniter's database config reads them from env vars:
// app/Config/Database.php — defaults are overridden by env
public array $default = [
'hostname' => '',
'username' => '',
'password' => '',
'database' => '',
'DBDriver' => 'MySQLi',
];If your platform injects a single DATABASE_URL, parse it in Database.php or map the individual database.default.* vars. On PandaStack you can create a MySQL database, link it, and set these env vars from the connection details shown in the dashboard.
Run migrations as a release step:
php spark migrate --allThe encryption key
CodeIgniter needs an encryption key for sessions and the encryption service. Generate one and set it as encryption.key:
php spark key:generateIn a containerized deploy, don't rely on key:generate writing to the env file (the filesystem is ephemeral). Instead, generate the key once locally and store it as a secret env var so it stays stable across deploys. A changing key invalidates all sessions on every release.
Performance flags
- OPcache: enabled via
docker-php-ext-install opcache. Addopcache.validate_timestamps=0in production so PHP doesn't stat files on every request. composer install --optimize-autoloader: builds a classmap.writable/permissions: must be writable (775) for cache, logs, and sessions, but keep it out of the web root — it already is by default.
Deploying
Push, connect the repo, link the database, set env vars. The platform builds your Dockerfile, deploys it, and provisions SSL for your custom domain. Live logs will surface any writable/ permission issues or missing extensions immediately.
git push origin mainConclusion
CodeIgniter rewards a disciplined deploy: serve only public/, flip CI_ENVIRONMENT to production, set a stable encryption key, and treat migrations as a release step. Do those four things and the framework's natural speed shines.
Want to try it without configuring servers? PandaStack's free tier includes a container service and a managed database — connect your CodeIgniter repo at [dashboard.pandastack.io](https://dashboard.pandastack.io) and it builds and deploys automatically.
References
- [CodeIgniter 4: Running Your App](https://codeigniter.com/user_guide/general/managing_apps.html)
- [CodeIgniter 4: Handling Multiple Environments](https://codeigniter.com/user_guide/general/environments.html)
- [CodeIgniter 4: Database Configuration](https://codeigniter.com/user_guide/database/configuration.html)
- [CodeIgniter 4: Encryption Service](https://codeigniter.com/user_guide/libraries/encryption.html)
- [PHP OPcache Configuration](https://www.php.net/manual/en/opcache.configuration.php)