Deploying a CodeIgniter App
What is CodeIgniter?
CodeIgniter is a lightweight, open-source PHP framework that provides a rich set of libraries and a logical structure to access these libraries. It is built for developers who need a simple, elegant, and powerful toolkit to create full-featured web applications.
Key features include:
- Lightweight and easy to understand MVC architecture
- Built-in security features (CSRF protection, XSS filtering, SQL injection prevention)
- Query Builder class for building database queries without writing raw SQL
- Powerful routing system with regex support
- RESTful API development support
- Flexible file upload handling
- Session and cookie management
- Form validation library
- Caching drivers (File, Redis, Memcached)
- Comprehensive logging system
- Error handling and debugging tools
- Templating engine (PHP-based views)
- Middleware support
- Database migration system
- Built-in testing frameworks
- Email library
- File management utilities
- Composer dependency management
- Extensive documentation and community
CodeIgniter is ideal for building web applications, RESTful APIs, content management systems, e-commerce platforms, real-time applications, and scalable web services.
Prerequisites
Before deploying a CodeIgniter application to Klutch.sh, ensure you have:
- PHP 7.4 or later (PHP 8.0+ recommended)
- Composer for dependency management
- Git and a GitHub account
- A Klutch.sh account with dashboard access
- PostgreSQL or MySQL for data persistence
- Basic understanding of PHP and MVC architecture
Getting Started with CodeIgniter
Step 1: Create a CodeIgniter Project
Using Composer, create a new CodeIgniter project:
composer create-project codeigniter4/appstarter codeigniter-appcd codeigniter-appThis creates a project structure:
codeigniter-app/├── app/│ ├── Controllers/│ ├── Models/│ ├── Views/│ ├── Config/│ ├── Filters/│ └── Libraries/├── public/│ └── index.php├── writable/│ ├── cache/│ ├── logs/│ └── uploads/├── tests/├── .env├── .env.example├── composer.json└── sparkStep 2: Configure Environment
Create a .env file from the example:
cp .env.example .envEdit .env:
# CI_ENVIRONMENT must be 'production' for live serversCI_ENVIRONMENT = development
# This variable must be changed to something else for production!ENCRYPTION_KEY =
# Database configurationdatabase.default.hostname = localhostdatabase.default.database = codeigniter_dbdatabase.default.username = rootdatabase.default.password = passworddatabase.default.DBDriver = MySQLi
# Or for PostgreSQL:# database.default.DBDriver = Postgre# database.default.hostname = localhost# database.default.port = 5432
# App settingsapp.baseURL = http://localhost:8080/app.indexPage = ''
# Session configurationsession.driver = filessession.cookieName = ci_session
# Logginglogging.threshold = 4Step 3: Create Database Model
Create app/Models/ItemModel.php:
<?php
namespace App\Models;
use CodeIgniter\Model;
class ItemModel extends Model{ protected $table = 'items'; protected $primaryKey = 'id'; protected $useAutoIncrement = true; protected $returnType = 'array'; protected $useSoftDeletes = false;
protected $allowedFields = ['name', 'description', 'price', 'created_at', 'updated_at'];
protected $useTimestamps = true; protected $dateFormat = 'datetime'; protected $createdField = 'created_at'; protected $updatedField = 'updated_at';
protected $validationRules = [ 'name' => 'required|min_length[1]|max_length[100]|is_unique[items.name]', 'description' => 'permit_empty|max_length[500]', 'price' => 'required|numeric|greater_than[0]', ];
protected $validationMessages = [ 'name' => [ 'required' => 'Name is required', 'is_unique' => 'This item name already exists', ], 'price' => [ 'required' => 'Price is required', 'numeric' => 'Price must be a number', ], ];
public function getItems($limit = 10, $offset = 0) { return $this->limit($limit, $offset)->findAll(); }
public function searchItems($query) { return $this->like('name', $query) ->orLike('description', $query) ->findAll(); }}Step 4: Create Database Migration
Create app/Database/Migrations/2024-01-01-000001_CreateItemsTable.php:
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateItemsTable extends Migration{ public function up() { $this->forge->addField([ 'id' => [ 'type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true, ], 'name' => [ 'type' => 'VARCHAR', 'constraint' => 100, 'unique' => true, ], 'description' => [ 'type' => 'TEXT', 'null' => true, ], 'price' => [ 'type' => 'INT', 'constraint' => 11, ], 'created_at' => [ 'type' => 'DATETIME', 'null' => true, ], 'updated_at' => [ 'type' => 'DATETIME', 'null' => true, ], ]);
$this->forge->addKey('id', true); $this->forge->addKey('name'); $this->forge->createTable('items'); }
public function down() { $this->forge->dropTable('items'); }}Step 5: Create API Controller
Create app/Controllers/Api/ItemsController.php:
<?php
namespace App\Controllers\Api;
use CodeIgniter\RESTful\ResourceController;use CodeIgniter\HTTP\ResponseInterface;use App\Models\ItemModel;
class ItemsController extends ResourceController{ protected $modelName = ItemModel::class; protected $format = 'json'; protected $helpers = ['form'];
public function health() { return $this->respond([ 'status' => 'healthy', 'service' => 'codeigniter-app' ], ResponseInterface::HTTP_OK); }
public function index() { $page = $this->request->getVar('page') ?? 1; $perPage = $this->request->getVar('per_page') ?? 10; $offset = ($page - 1) * $perPage;
$items = $this->model->getItems($perPage, $offset); $total = $this->model->countAll();
return $this->respond([ 'data' => $items, 'pagination' => [ 'total' => $total, 'page' => $page, 'per_page' => $perPage, 'total_pages' => ceil($total / $perPage) ] ], ResponseInterface::HTTP_OK); }
public function show($id = null) { $item = $this->model->find($id);
if (!$item) { return $this->failNotFound('Item not found'); }
return $this->respond($item, ResponseInterface::HTTP_OK); }
public function create() { $data = $this->request->getJSON(true);
if (!$this->model->insert($data)) { return $this->fail($this->model->errors(), ResponseInterface::HTTP_BAD_REQUEST); }
$id = $this->model->getInsertID(); $item = $this->model->find($id);
return $this->respondCreated($item); }
public function update($id = null) { $item = $this->model->find($id);
if (!$item) { return $this->failNotFound('Item not found'); }
$data = $this->request->getJSON(true);
if (!$this->model->update($id, $data)) { return $this->fail($this->model->errors(), ResponseInterface::HTTP_BAD_REQUEST); }
return $this->respond($this->model->find($id), ResponseInterface::HTTP_OK); }
public function delete($id = null) { $item = $this->model->find($id);
if (!$item) { return $this->failNotFound('Item not found'); }
$this->model->delete($id);
return $this->respondDeleted(['id' => $id]); }
public function stats() { return $this->respond([ 'total_items' => $this->model->countAll(), 'timestamp' => time() ], ResponseInterface::HTTP_OK); }}Step 6: Configure Routes
Edit app/Config/Routes.php:
<?php
namespace Config;
use CodeIgniter\Router\RouteCollection;
/** * @var RouteCollection $routes */$routes->setDefaultNamespace('App\Controllers');
// Health check$routes->get('health', 'Home::health');
// API Routes$routes->group('api', ['namespace' => 'App\Controllers\Api'], function ($routes) { $routes->get('items/health', 'ItemsController::health'); $routes->get('items/stats', 'ItemsController::stats'); $routes->resource('items', ['controller' => 'ItemsController']);});
$routes->get('/', 'Home::index');Step 7: Create Home Controller
Edit app/Controllers/Home.php:
<?php
namespace App\Controllers;
class Home extends BaseController{ public function health() { return $this->response->setJSON([ 'status' => 'healthy', 'service' => 'codeigniter-app' ]); }
public function index() { return view('welcome_message'); }}Step 8: Update Composer Dependencies
Edit composer.json to ensure required packages:
{ "name": "codeigniter4/appstarter", "type": "project", "description": "CodeIgniter4 Application Starter", "require": { "php": "^7.4 || ^8.0", "codeigniter4/framework": "^4.4" }, "require-dev": { "mikey179/phpunit-mock-objects": "^3.0", "phpunit/phpunit": "^9.1" }}Step 9: Build and Test Locally
# Install dependenciescomposer install
# Create .env file with database configurationcp .env.example .env
# Run database migrationsphp spark migrate
# Start the development serverphp spark serve
# Test the APIcurl http://localhost:8080/api/items/healthDeploying Without a Dockerfile
Klutch.sh uses Nixpacks to automatically detect and build your CodeIgniter application from your source code.
Prepare Your Repository
- Initialize a Git repository and commit your code:
git initgit add .git commit -m "Initial CodeIgniter app commit"- Create a
.gitignorefile:
.env.env.production.env.local.vscode/.idea/writable/cache/*writable/logs/*writable/uploads/*vendor/composer.lock.DS_Store*.lognode_modules/- Update your
.env.examplewith all configuration variables:
CI_ENVIRONMENT = productionENCRYPTION_KEY =app.baseURL = https://example-app.klutch.sh/database.default.hostname = {DB_HOSTNAME}database.default.database = {DB_DATABASE}database.default.username = {DB_USERNAME}database.default.password = {DB_PASSWORD}-
Ensure your project structure includes:
public/index.phpas the entry pointcomposer.jsonfor dependencies.env.examplefor configuration template
-
Push to GitHub:
git remote add origin https://github.com/YOUR_USERNAME/codeigniter-app.gitgit branch -M maingit push -u origin mainDeploy to Klutch.sh
-
Log in to Klutch.sh dashboard.
-
Click “Create a new project” and provide a project name.
-
Inside your project, click “Create a new app”.
-
Repository Configuration:
- Select your GitHub repository containing the CodeIgniter app
- Select the branch to deploy (typically
main)
-
Traffic Settings:
- Select “HTTP” as the traffic type
-
Port Configuration:
- Set the internal port to 8080 (the recommended port for PHP applications)
-
Environment Variables: Set the following environment variables in the Klutch.sh dashboard:
CI_ENVIRONMENT: Set toproductionfor live deploymentsdatabase.default.hostname: Your database hostdatabase.default.database: Your database namedatabase.default.username: Your database userdatabase.default.password: Your database passwordENCRYPTION_KEY: A strong random encryption key for CodeIgniterapp.baseURL: Your deployed app URL (e.g.,https://example-app.klutch.sh/)
-
Build and Start Commands (Optional): If you need to customize the build or start command, set these environment variables:
BUILD_COMMAND: Default runscomposer install && php spark migrateSTART_COMMAND: Default isphp spark serve --host 0.0.0.0 --port 8080
-
Region, Compute, and Instances:
- Choose your desired region for optimal latency
- Select compute resources (Starter for prototypes, Pro/Premium for production)
- Set the number of instances (start with 1-2, scale as needed based on traffic)
-
Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.
-
Once deployment completes, your app will be accessible at
example-app.klutch.sh.
Verifying the Deployment
Test your deployed app:
curl https://example-app.klutch.sh/healthYou should receive:
{ "status": "healthy", "service": "codeigniter-app"}Access the API:
https://example-app.klutch.sh/api/itemsDeploying With a Dockerfile
If you prefer more control over your build environment, you can provide a custom Dockerfile. Klutch.sh automatically detects and uses a Dockerfile in your repository’s root directory.
Create a Dockerfile
Create a Dockerfile in your project root:
# Build stageFROM composer:2.6 as builder
WORKDIR /app
# Copy composer filesCOPY composer.json composer.lock ./
# Install dependenciesRUN composer install --no-dev --optimize-autoloader
# Runtime stageFROM php:8.2-fpm-alpine
WORKDIR /app
# Install required PHP extensionsRUN apk add --no-cache \ postgresql-dev \ mysql-dev \ nginx \ curl
RUN docker-php-ext-install \ pdo \ pdo_mysql \ pdo_pgsql \ mysqli
# Copy application from builderCOPY --from=builder /app/vendor ./vendorCOPY --chown=www-data:www-data . .
# Create necessary directoriesRUN mkdir -p writable/cache writable/logs writable/uploads && \ chown -R www-data:www-data writable public
# Configure NginxRUN echo "server { \ listen 8080; \ server_name _; \ root /app/public; \ index index.php; \ client_max_body_size 100M; \ 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; \ } \}" > /etc/nginx/conf.d/default.conf
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1
# Expose portEXPOSE 8080
# Create non-root userRUN addgroup -g 1000 codeigniter && \ adduser -D -u 1000 -G codeigniter codeigniter
USER codeigniter
# Start applicationCMD ["sh", "-c", "php-fpm & nginx -g 'daemon off;'"]Deploy the Dockerfile Version
- Push your code with the Dockerfile to GitHub:
git add Dockerfilegit commit -m "Add Dockerfile for custom build"git push-
Log in to Klutch.sh dashboard.
-
- Select your GitHub repository and branch
- Set traffic type to “HTTP”
- Set the internal port to 8080
- Add environment variables
- Click “Create”
-
Klutch.sh will automatically detect your Dockerfile and use it for building and deployment.
Database Configuration
PostgreSQL Setup
PostgreSQL is recommended for CodeIgniter applications. Configure in .env:
database.default.DBDriver = Postgredatabase.default.hostname = {DB_HOSTNAME}database.default.port = 5432database.default.database = {DB_DATABASE}database.default.username = {DB_USERNAME}database.default.password = {DB_PASSWORD}MySQL Setup
Configure MySQL in .env:
database.default.DBDriver = MySQLidatabase.default.hostname = {DB_HOSTNAME}database.default.port = 3306database.default.database = {DB_DATABASE}database.default.username = {DB_USERNAME}database.default.password = {DB_PASSWORD}Running Migrations
Migrations run automatically on deployment or manually with:
php spark migratephp spark seed:runSession and Cookie Management
Configure Sessions
Edit app/Config/Session.php:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;use CodeIgniter\Session\Handlers\DatabaseHandler;use CodeIgniter\Session\Handlers\FileHandler;
class Session extends BaseConfig{ public string $sessionDriver = 'CodeIgniter\Session\Handlers\FileHandler';
// Or use database handler // public string $sessionDriver = 'CodeIgniter\Session\Handlers\DatabaseHandler';
public string $sessionCookieName = 'ci_session'; public int $sessionExpiration = 7200; public bool $sessionSavePath = WRITABLEPATH . 'session'; public bool $sessionMatchIP = false; public bool $sessionTimeToUpdate = 300; public bool $sessionRegenerateDestroy = true;}Session Usage
$session = session();
// Set session data$session->set('user_id', $userId);$session->set(['username' => $username, 'email' => $email]);
// Get session data$userId = $session->get('user_id');$userData = $session->getFlashdata('user_data');
// Remove session data$session->remove('user_id');File Uploads
Configure Uploads
Edit app/Config/Files.php:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Files extends BaseConfig{ public int $fileSize = 100; public int $uploadDir = WRITABLEPATH . 'uploads';}Handle File Uploads
public function uploadFile(){ $file = $this->request->getFile('file');
if ($file->isValid() && !$file->hasMoved()) { $newName = $file->getRandomName(); $file->move(WRITABLEPATH . 'uploads', $newName);
return $this->respond([ 'message' => 'File uploaded successfully', 'filename' => $newName, 'size' => $file->getSize() ]); }
return $this->fail('File upload failed');}Caching Configuration
Redis Caching
Configure Redis in app/Config/Cache.php:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Cache extends BaseConfig{ public string $handler = 'redis';
public array $redis = [ 'host' => getenv('REDIS_HOST') ?? 'localhost', 'password' => getenv('REDIS_PASSWORD') ?? null, 'port' => getenv('REDIS_PORT') ?? 6379, 'timeout' => 0, 'database' => 0, ];
public int $ttl = 3600;}Using Cache
$cache = cache();
// Save to cache$cache->save('items', $items, 3600);
// Retrieve from cache$items = $cache->get('items');
// Delete from cache$cache->delete('items');Form Validation
Validate Form Data
public function store(){ if (!$this->validate([ 'name' => 'required|min_length[3]|max_length[100]', 'email' => 'required|valid_email', 'age' => 'required|numeric|greater_than[0]', ])) { return $this->fail($this->validator->getErrors()); }
$data = $this->request->getPost(); $this->itemModel->insert($data);
return $this->respondCreated($data);}Logging and Error Handling
Configure Logging
Edit app/Config/Logger.php:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;use CodeIgniter\Log\Handlers\FileHandler;
class Logger extends BaseConfig{ public string $logPath = WRITABLEPATH . 'logs/'; public string $threshold = 3;
public array $handlers = [ 'CodeIgniter\Log\Handlers\FileHandler' => [ 'handles' => ['critical', 'alert', 'emergency', 'error', 'warning', 'notice', 'info', 'debug'], ], ];}Use Logging
$logger = service('logger');
$logger->info('Item created: ' . $itemId);$logger->error('Database error occurred', ['error' => $error]);$logger->debug('Debug information', $debugData);Persistent Storage for Uploads and Logs
Adding Persistent Volume
- In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
- Click “Add Volume”
- Set the mount path:
/app/writable(for uploads and cache/logs) - Set the size based on your needs (e.g., 20 GB for uploads and logs)
- Save and redeploy
Configure Writable Paths
The writable directory should be mounted as persistent storage:
# Ensure writable directory permissionschmod -R 755 writablechmod -R 777 writable/cache writable/logs writable/uploadsCustom Domains
To serve your CodeIgniter application from a custom domain:
- In the Klutch.sh app dashboard, navigate to “Custom Domains”
- Click “Add Custom Domain”
- Enter your domain (e.g.,
api.example.com) - Follow the DNS configuration instructions provided
Example DNS configuration:
api.example.com CNAME example-app.klutch.shTroubleshooting
Issue 1: Database Connection Error
Problem: “Unable to connect to database” during deployment.
Solution:
- Verify database connection credentials in environment variables
- Ensure PostgreSQL/MySQL instance is running and accessible
- Check database user has proper permissions
- Test connection string format
- Verify firewall rules allow the connection
Issue 2: File Upload Permission Denied
Problem: File uploads fail with permission denied errors.
Solution:
- Mount persistent storage for
writabledirectory - Set proper directory permissions:
chmod 777 writable/uploads - Ensure web server user has write permissions
- Check disk space availability
- Verify file size limits
Issue 3: Session Data Lost
Problem: Session data is not persisting between requests.
Solution:
- Configure persistent storage for session files
- Use database session handler for distributed systems
- Verify session cookie settings in config
- Check browser cookie settings
- Ensure session path is writable
Issue 4: Slow Application Performance
Problem: Application responds slowly to requests.
Solution:
- Enable caching for frequently accessed data
- Optimize database queries and add indexes
- Use Redis for session and cache storage
- Enable query result caching
- Monitor performance metrics in dashboard
- Consider scaling to additional instances
Issue 5: Memory or Disk Space Issues
Problem: Application crashes due to memory or disk space limits.
Solution:
- Increase instance compute resources
- Increase persistent storage size
- Optimize database queries
- Clean up old log files
- Implement file cleanup routines
- Monitor resource usage via dashboard
Best Practices for Production Deployment
-
Environment Configuration: Use
.envfor sensitive dataCI_ENVIRONMENT = productiondatabase.default.password = {STRONG_PASSWORD}ENCRYPTION_KEY = {RANDOM_KEY} -
Enable HTTPS: Always use HTTPS in production
app.forceGlobalSecureRequests = true; -
Input Validation: Validate all user inputs
$this->validate(['name' => 'required|min_length[3]']); -
SQL Injection Prevention: Use Query Builder or prepared statements
$this->db->select('*')->from('items')->where('id', $id)->get(); -
CSRF Protection: Enable CSRF protection (enabled by default)
filters => ['csrf'] -
Error Handling: Don’t expose sensitive error details
if (!$item) {return $this->failNotFound('Resource not found');} -
Database Optimization: Add indexes to frequently queried columns
CREATE INDEX idx_name ON items(name); -
Session Security: Use secure session configuration
sessionSavePath = '/tmp/sessions';sessionRegenerateDestroy = true; -
Logging: Log important events and errors
$logger->info('User login: ' . $userId); -
Rate Limiting: Implement rate limiting for APIs
if ($this->request->getIPAddress() === $cachedIP) {return $this->fail('Too many requests', 429);}
Resources
- CodeIgniter Official Website
- CodeIgniter User Guide
- CodeIgniter Models Documentation
- CodeIgniter Routing Guide
- Form Validation Documentation
- Database Documentation
- Logging Documentation
Conclusion
Deploying CodeIgniter applications to Klutch.sh provides a lightweight, flexible platform for building and running PHP web applications. CodeIgniter’s simplicity, security features, and powerful tools make it ideal for developers building custom solutions.
Key takeaways:
- Use Nixpacks for quick deployments with automatic PHP detection
- Use Docker for complete control over PHP extensions and dependencies
- Leverage CodeIgniter’s Query Builder for safe database queries
- Implement proper form validation for all user inputs
- Use caching strategies to improve performance
- Configure persistent storage for file uploads and logs
- Enable HTTPS and CSRF protection in production
- Implement structured logging for monitoring and debugging
- Use environment variables for configuration management
- Monitor application health and performance metrics
For additional help, refer to the CodeIgniter documentation or Klutch.sh support resources.