Deploying your CakePHP applications in production using Docker

More and more PHP developers finding the way using containers in their dev and test workflow. It was about time to write a small piece how to deploy a PHP application on Cloud 66 for Docker.

For this blog post I'm going to focus on a CakePHP based PHP applications and the composer package manager.

Let's assume you have beautifully crafted CakePHP app on your local box. The first step is to containerize this puppy. You can download my example from github

Dockerfile

The first step is to create a Dockerfile in your root folder. For this example, we are using the php-apache base image. It's a good start to deploy your LAMP stack. You can check docker hub for all the versions and variants.

With no further ado, here's the Dockerfile. Please read the comments what is going on in each step.

#start with our base image (the foundation) - version 7.1.5
FROM php:7.1.5-apache

#install all the system dependencies and enable PHP modules 
RUN apt-get update && apt-get install -y \  
      libicu-dev \
      libpq-dev \
      libmcrypt-dev \
      mysql-client \
      git \
      zip \
      unzip \
    && rm -r /var/lib/apt/lists/* \
    && docker-php-ext-configure pdo_mysql --with-pdo-mysql=mysqlnd \
    && docker-php-ext-install \
      intl \
      mbstring \
      mcrypt \
      pcntl \
      pdo_mysql \
      pdo_pgsql \
      pgsql \
      zip \
      opcache

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

#set our application folder as an environment variable
ENV APP_HOME /var/www/html

#change uid and gid of apache to docker user uid/gid
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data

#change the web_root to cakephp /var/www/html/webroot folder
RUN sed -i -e "s/html/html\/webroot/g" /etc/apache2/sites-enabled/000-default.conf

# enable apache module rewrite
RUN a2enmod rewrite

#copy source files and run composer
COPY . $APP_HOME

# install all PHP dependencies
RUN composer install --no-interaction

#change ownership of our applications
RUN chown -R www-data:www-data $APP_HOME

NOTE We didn't explicitly specify the CMD to tell Docker how to run our container. But the base-image (FROM php:7.1.5-apache) has the command to run Apache in the foreground, we simply inherit the command.

Of course, you want to test your containerized application on your local machine. The best way to start your composition of your PHP application together with your backend services (like MySQL, RabbitMQ and so on) it to use docker-compose.

Docker-Compose

Check the docker-compose.yml we are using:

version: '2'  
# define all services
services:  
  # our service is called CakePHP ;-)
  cakephp:
    # we want to use the image which is build from our Dockerfile
    build: .
    # apache is running on port 80 but we want to expose this to port 4000 on our local machine
    ports:
      - "4000:80"
    # we depending on the mysql backend
    depends_on:
      - mysql
    # we mount the working dir into the container, handy for development 
    volumes:
      - .:/var/www/html/
    environment:
      - SECURITY_SALT=ashjg23697sds97139871298ashk  
      - MYSQL_URL=mysql
      - MYSQL_USERNAME=root
      - MYSQL_PASSWORD=root
  mysql:
    # we use the mysql base image, version 5.6.36
    image: mysql:5.6.36
    # we mount a datavolume to make sure we don't loose data
    volumes:
       - mysql_data:/var/lib/mysql
    # setting some envvars to create the DB
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=cakephp
volumes:  
    mysql_data:

First, we need to build the image of the container.

docker-compose build

After the building is done we can start our service(s) composition, using the up command:

docker-compose up

The above command will start MySQL and CakePHP on port 4000 on your localhost. We mounted our source dir with .:/var/www/html/ meaning we overwrite the container filesystem with our source dir. And our source dir doesn't has /vendor populated. Let's run composer.

Stop our services:

docker-compose down

Run the following command to run composer.

docker-compose run cakephp composer install --no-interaction

And now our /vendor directory is populated. Start the services again:

docker-compose up

But if we hit http://localhost:4000/bookmarks do we get an error? Make sure your set the app.php correctly. Pay attention to the DB settings:

 'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'username' => env('MYSQL_USERNAME', 'root'),
            'password' => env('MYSQL_PASSWORD', ''),
            'database' => 'cakephp',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'flags' => [],
            'cacheMetadata' => true,
            'log' => false,
            'quoteIdentifiers' => false,
            'host' => env('MYSQL_URL', null),
        ],

Look closely at the DB settings. The env('MYSQL_URL', null) is using the DNS name mysql which translate to the IP address of the running MySQL container, this is called DNS based service discovery.

The other settings are related to the settings in the docker-compose.yml. Of course you can add the environment variables directly to the docker-compose.yml.

Of course we need to setup your database. Just run the migration command like this:

docker-compose run cakephp bin/cake migrations migrate

and we need to seed the database with a user-name and password:

docker-compose run cakephp bin/cake migrations seed

Hit http://localhost:4000/bookmarks and login with user john.doe@example.com and password password and you can start adding bookmarks.

Time to bring it to production! Yeah.

Running in production using Cloud 66

Make sure you committed the Dockerfile in your root folder of your git repo. Create a new Cloud 66 account and create a new Cloud 66 for Containers stack.

We give our stack a name (example-cakephp) and pop in our git repo. If you hit GO we analyse your codebase and make sure your Dockerfile is good to go.

The next step is to build the image ready for deployment. This is part of our CI solution. You don't need to build and push your own images!

Time to tell Cloud 66 how to deploy your CakePHP service and what kind of backend we want to provision. Good to know we run your backend services, not in containers but natively, giving you all the cool feature of our platform like backup/restore and scaling of the DB's.
We support all mayor cloud providers or bring your own server. The servers are yours.

Depending on your workload, you can choice which server size you want to use.

We need to expose our service to the internet and tell we map the container port to our public internet port.

We hit Deploy Stack and let Cloud 66 do all the heavy lifting provisioning all the servers and components. After a couple of minutes, your stack is ready!

But what about the migrations and environment variables? We provide you with a feature to specify your environment variables and tell which deploy_command to run before starting the CakePHP service. With the Configuration Files we can set our deploy_command in service.yml

After a Redeploy we can hit the endpoint of our service (a simple bookmarks app) and start adding bookmarks.

If you are ready to bring your PHP application into production, we provide you with all the tools to scale your service (more PHP instances for more concurrent connections) and scale-out your application and db cluser. With just a click of a button. Don't forget to add some SSL certificates. We also take care of this.

Summary

This was a quick overview how to deploy your CakePHP or any other PHP application in production using Docker and Cloud 66. You can use the example Dockerfile and docker-compose.yml to start on your local machine first and move it to production, on any cloud, when you are ready.

If you want to know more about what our platform has to offer, check our feature here.

Happy PHP coding!

Try Cloud 66 for Free, No credit card required