Dockerizing a PHP Application

  • Containers are portable and can be deployed instantly anywhere.
  • Containers bring developers a uniform and streamlined work environment that can be easily shared.
  • Containers are the first step towards running your application with high availability with Docker Swarm or Kubernetes.
  • Install Docker
  • Run Docker images
  • Build customer images to run programs
  • Use Docker Compose to set up a dev environment
  • Run our application in Heroku
  • Test our application with Continuous Integration (CI)
  • Deploy our application with Continous Deployment (CD)
  • Install Git in your machine.
  • Sign up with GitHub.
  • Go to the demo application.
  • Use the Fork button to copy the repository in your account:
  • Clone the repository in your machine. Open a terminal and type:
$ git clone YOUR_REPOSITORY_URL
$ cd semaphore-demo-php-unsplash

Run the Demo

  • PHP 7
  • The composer package manager.
  • One Unsplash API Key.
  1. Sign up to Unsplash.
  2. Go to Applications.
  3. Select New Application.
  4. Review and accept the Usage Terms.
  5. Set a name for the application.
  6. Copy the Access Key and the Secret Key shown.
  1. Prepare the application environment and install the dependencies:
$ cd src
$ composer install
$ cp .env.example .env
$ cp .env.example.unsplash .env-unsplash
$ php artisan key:generate
  • Edit the .env-unsplash file.
  • Type in the Access and Secret Keys next to the variables.
export UNSPLASH_ACCESS_KEY="YOUR ACCCESS KEY"
export UNSPLASH_SECRET_KEY="YOUR SECRET KEY"
  • Source the file and start the application:
$ source .env-unsplash
$ php artisan serve

What is Docker?

Prerequisites

Installing Docker

Docker Desktop

$ apt-get update && apt-get install docker

Docker Images

Working with Docker Images

$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu 14.04 91e54dfb1179 5 months ago 188.4 MB
nimmis/apache-php7 latest bdd370e4f83b 6 months ago 484.4 MB
eboraas/apache-php latest 0501b3fdd0c2 6 months ago 367 MB
mysql latest a128139aadf2 6 months ago 283.8 MB
ubuntu latest d2a0ecffe6fa 7 months ago 188.4 MB
eboraas/laravel latest 407e2d00b528 12 months ago 404.5 MB

Docker Containers

$ docker run -d php:7.4-apache
c6fbefcd630a2f4c970792af0302d9c25fe9118cec85091b04e75e7c942f5686
$ docker psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
c6fbefcd630a php:7.4-apache "docker-php-entrypoi…" 39 seconds ago Up 38 seconds 80/tcp laughing_lalande
$ docker run -tid --name="apache_server" php:7.4-apache
fdae121b23e13690fedaab4636311d8ab6b35f32fa4c68e1c98726578de35a66
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fdae121b23e1 php:7.4-apache "docker-php-entrypoi…" 16 seconds ago Up 15 seconds 80/tcp apache_server
c6fbefcd630a php:7.4-apache "docker-php-entrypoi…" About a minute ago Up About a minute 80/tcp laughing_lalande
$ docker exec -it apache_server bash(you're now running a session inside the container)
$ /etc/init.d/apache2 status
[ ok ] apache2 is running.
# Delete container using ID or name
docker rm -f <container-id-or-name>
# Delete all available containers
docker rm -f $(docker ps -aq)
$ docker run -tid \
-p 8000:80 \
--name apache_server \
php:7.4-apache
$ docker inspect \
-f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
CONTAINER_ID_OR_NAME
172.19.0.2
$ docker run -tid \
-p 8000:80 \
--name apache_server \
-v YOUR_HOST_WWW_ROOT:/var/www/html \
php:7.4-apache

Working with Dockerfiles

$ cd ..
# 000-default.conf<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/public
<Directory /var/www>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
#!/usr/bin/env bash
sed -i "s/Listen 80/Listen ${PORT:-80}/g" /etc/apache2/ports.conf
sed -i "s/:80/:${PORT:-80}/g" /etc/apache2/sites-enabled/*
apache2-foreground
$ chmod 755 start-apache
# Dockerfile
FROM php:7.4-apache
...COPY 000-default.conf /etc/apache2/sites-available/000-default.conf...
...RUN a2enmod rewrite...
...COPY src /var/www/
RUN chown -R www-data:www-data /var/www
...
CMD ["executable","param1","param2"]
...CMD ["start-apache"]
FROM php:7.4-apacheCOPY 000-default.conf /etc/apache2/sites-available/000-default.conf
COPY start-apache /usr/local/bin
RUN a2enmod rewrite
# Copy application source
COPY src /var/www/
RUN chown -R www-data:www-data /var/www
CMD ["start-apache"]

Useful Commands

RUN apt-get update && \
apt-get install nodejs
ENV MYSQL_ROOT_PASSWORD=root
ENV MYSQL_ROOT_USER=root

Building the Image

$ docker build .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 19c684978566 20 seconds ago 451MB
php 7.4-apache 0c37fe4343a5 2 weeks ago 414MB
$ docker tag SOURCE_IMAGE:TAG TARGET_IMAGE:TAG
$ docker tag 19c684978566 YOUR_DOCKERHUB_USER/semaphore-demo-php-unsplash
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomfern/semaphore-demo-php-unsplash latest 19c684978566 3 minutes ago 451MBMB
$ docker login
$ docker push YOUR_DOCKERHUB_USER/semaphore-demo-php-unsplash
  • After logging into our Docker Hub account, you should see the new image in the repository:

Docker Compose

# docker-compose.yml
version: "3.9"
services:
webapp:
build:
context: .
dockerfile: ./Dockerfile.development
...
# docker-compose.yml
version: "3.9"
services:
webapp:
image: YOUR_DOCKERHUB_USER/semaphore-demo-php-unsplash
... ports:
- "8000:80"
volumes:
- ./src:/var/www
environment:
- APP_KEY=SomeRandomStringToAddSecurity123
- APP_ENV=development
- APP_DEBUG=true
- APACHE_RUN_USER=apache-www-volume
- APACHE_RUN_GROUP=apache-www-volume
- UNSPLASH_ACCESS_KEY=${UNSPLASH_ACCESS_KEY}
- UNSPLASH_SECRET_KEY=${UNSPLASH_SECRET_KEY}
version: "3.9"
services:
webapp:
build:
context: .
dockerfile: ./Dockerfile.development
ports:
- "8000:80"
volumes:
- ./src:/var/www
environment:
- APP_KEY=SomeRandomStringToAddSecurity123
- APP_ENV=development
- APP_DEBUG=true
- APACHE_RUN_USER=apache-www-volume
- APACHE_RUN_GROUP=apache-www-volume
- UNSPLASH_ACCESS_KEY=${UNSPLASH_ACCESS_KEY}
- UNSPLASH_SECRET_KEY=${UNSPLASH_SECRET_KEY}
# Dockerfile.development
FROM php:7.4-apache
# Setup Apache2 config
COPY 000-default.conf /etc/apache2/sites-available/000-default.conf
RUN a2enmod rewrite
CMD ["apache2-foreground"]
$ source .env-unsplash
$ docker compose up --build
Starting semaphore-demo-php-unsplash_webapp_1 ... done
Attaching to semaphore-demo-php-unsplash_webapp_1
webapp_1 | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.19.0.2. Set the 'ServerName' directive globally to suppress this message
webapp_1 | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.19.0.2. Set the 'ServerName' directive globally to suppress this message
webapp_1 | [Fri Jan 17 13:38:04.382337 2020] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.38 (Debian) PHP/7.4.1 configured -- resuming normal operations
webapp_1 | [Fri Jan 17 13:38:04.382375 2020] [core:notice] [pid 1] AH00094: Command line: 'apache2 -DFOREGROUND'
$ docker ps -aCONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS              PORTS                           NAMES
7ec590488723 semaphore-demo-php-unsplash_webapp "docker-php-entrypoi…" 45 minutes ago Up 27 minutes 443/tcp, 0.0.0.0:8000->80/tcp semaphore-demo-php-unsplash_webapp_1
$ git add docker-compose.yml 000-default.conf Dockerfile* start-apache
$ git commit -m "add docker and apache config"
$ git push origin master

Using Docker with Heroku

  1. Sign up with Heroku.
  2. Click on your account portrait and then on Account.
  3. Scroll down until the API Keys section. Request an API Key and copy the value. We’ll need it later.
  1. Create a New Application, remember its name for later.
  2. Install the Heroku CLI in your machine and log in. This will open a browser window for you to login:
$ heroku login
$ heroku container:login
  1. Heroku has its own Docker registry. We have to tag and push the image using your application name:
$ docker tag YOUR_DOCKERHUB_USERNAME/semaphore-demo-php-unsplash registry.heroku.com/YOUR_HEROKU_APP_NAME/web
$ docker push registry.heroku.com/YOUR_HEROKU_APP_NAME/web
  1. Set the environment variables for the application. The APP_KEY should be a random 32 character string:
$ heroku config:set UNSPLASH_ACCESS_KEY=YOUR_UNSPLASH_ACCESS_KEY
$ heroku config:set UNSPLASH_SECRET_KEY=YOUR_UNSPLASH_SECRET_KEY
$ heroku config:set APP_ENV=production
$ heroku config:set APP_KEY=SomeRandomStringToAddSecurity123
  1. Finally, enable some Docker optimizations and release the application:
$ heroku labs:enable --app=YOUR_HEROKU_APP_NAME runtime-new-layer-extract
$ heroku stack:set container --app YOUR_HEROKU_APP_NAME
$ heroku container:release web --app YOUR_HEROKU_APP_NAME

Continuous Integration With Semaphore

  1. Head to Semaphore and sign up using the Sign up with GitHub button.
  2. The next step is to load your Unsplash Access Key to Semaphore. To securely store sensitive information, Semaphore provides the secrets feature. When we reference a secret in Semaphore, it’s automatically decrypted and made available:
  3. On the left navigation menu, click on Secrets below Configuration.
  4. Click Create New Secret.
  5. Create the environment variables as shown, the name of the secret should be unsplash-api:
  1. Create a second secret to store Docker Hub credentials:
  1. Create a third and final secret called “heroku” to store the Heroku API Key:
  1. On the left navigation menu, click on the + (plus sign) next to Projects:
  2. Find the demo repository and click on Choose:
  3. Select the I will use the existing configuration option. The demo ships with a starter configuration.
  4. Semaphore will pick up any existing CI configuration once we make a modification: edit any file (for example, the README), commit and push the change to the repository.
  5. Go back to Semaphore to see that the CI workflow has already started:
  • Click on Edit Workflow to open the Workflow Builder.
  • Click on the main grey box called CI Pipeline. The main pipeline components are:
  • Pipelines: A pipeline fulfills a specific task, such as testing and deploying. Pipelines are made of blocks that are executed from left to right.
  • Agent: The agent is the virtual machine that powers the pipeline. We have three machine types to choose from. The machine runs an optimized Ubuntu 20.04 image with build tools for many languages.
  • Blocks: Blocks are made of jobs that have a shared configuration and purpose, for example, building or testing. One all jobs in a block complete, the next block can begin.
  • Jobs: Jobs contain commands that do the work. Jobs within a block run in parallel, each one in its own separate environment.
  • checkout: clones the GitHub repository into the CI machine. Most jobs will do a checkout at the start.
  • cache: automatically detects the project structure and store PHP modules in the Semaphore cache. cache restore retrieve the files to avoid having to download them again.

Continuous Deployment on Semaphore

  • Dockerize: to build a production Docker image.
  • Deploy: to deploy the image to Heroku.
  1. Click on + Add Promotion.
  2. Name the promotion: “Dockerize”
  3. Check the Enable automatic promotion option:
  4. Scroll right and name the pipeline: “Docker build”.
  5. Click on the new block and change its name to: “Docker build”.
  6. Open the Prologue and type the following contents:
checkout
cd src
cache restore
composer install --no-dev
cd ..
  1. Set the name of the job to “Build” and type the following commands:
echo "$DOCKER_PASSWORD" | docker login  --username "$DOCKER_USERNAME" --password-stdin
echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin
docker pull "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:latest || true
docker build --cache-from "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:latest -t "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:$SEMAPHORE_WORKFLOW_ID .
docker push "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:$SEMAPHORE_WORKFLOW_ID
  1. Open the Secrets section and select dockerhub:
  2. Click on Run the Workflow and then on Start:

The Semaphore Container Registry

$ git pull origin master
FROM php:7.4-apacheCOPY 000-default.conf /etc/apache2/sites-available/000-default.conf
COPY start-apache /usr/local/bin
RUN a2enmod rewrite
COPY src /var/www/
RUN chown -R www-data:www-data /var/www
CMD ["start-apache"]
$ git add Dockerfile
$ git commit -m "use semaphore docker registry"
$ git push origin master

Deployment Pipeline

  1. Press Edit Workflow again.
  2. Scroll right and use + Add Promotion. Name the promotion: “Deploy to Heroku”
  3. Click on the new pipeline, call it: “Deploy to Heroku”.
  4. Select the block, let’s name it: “Deploy”.
  5. Open Environment Variables and set the variable HEROKU_APP to your Heroku application name.
  6. Open Secrets and check dockerhub, heroku, and app-env.
  7. Name the job “Deploy” and type the following commands in the box:
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
docker pull "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:$SEMAPHORE_WORKFLOW_ID
docker tag "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:$SEMAPHORE_WORKFLOW_ID registry.heroku.com/$HEROKU_APP/web
heroku container:login
docker push registry.heroku.com/$HEROKU_APP/web
heroku config:set UNSPLASH_ACCESS_KEY="$UNSPLASH_ACCESS_KEY"
heroku config:set UNSPLASH_SECRET_KEY="$UNSPLASH_SECRET_KEY"
heroku config:set APP_ENV=production
heroku config:set APP_KEY=qiRKsBnNoFwwOo77rDVJbK1N6IQyBKHf
heroku labs:enable --app=$HEROKU_APP runtime-new-layer-extract
heroku stack:set container --app $HEROKU_APP
heroku container:release web --app $HEROKU_APP
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
docker pull "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:$SEMAPHORE_WORKFLOW_ID
docker tag "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:$SEMAPHORE_WORKFLOW_ID "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:latest
docker pull "$DOCKER_USERNAME"/semaphore-demo-php-unsplash:latest
  1. Add a second block called “Tag latest image”.
  2. On Secrets, select dockerhub
  3. Type the following commands in the job box:
  4. Click on Run the Workflow and Start.

Conclusion

--

--

--

Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Semaphore

Semaphore

Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com

More from Medium

Senior Engineer at ContactOut

Get started with Symfony 6 for beginners — Part 2 | Rout, Controller, Rendering.

Top 5 Best PHP Frameworks to use in 2022

5 Best PHP Frameworks

Scheduling Execution in PHP