In Action Tutorial Series - Docker - PHP Development with Docker

A. Getting started

A.1. Brief explanation

A.1.1. Image

Image is a package which includes code, a runtime, libraries, environment variables, and config files (Dockerfile). Get from http://hub.docker.com Types:

  • By source: offical and public/user image
  • By level: scratch, base os and application image

A.1.2. Container

Container is a running instance of a image. It is isolated from host env, only accessing host files and ports if configured

A.1.3. Docker compose

Docker compose is a tool for managing many docker container in a central config file (docker-compose.yml)

A.2. Installing

Install docker

To install AUFS storage driver. Others drivers: Btrts, Device mapper, VFS, ZFS

$ apt-get install \
    linux-image-extra-$(uname -r) \
    linux-image-extra-virtual
    $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -

Add docker repository

$ add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

Install docker ce

$ apt-get install docker-ce

Check

$ docker -v
// Docker version 17.03.1-ce, build c6d412e

Install docker-compose

Get binary

$ curl -L https://github.com/docker/compose/releases/download/1.12.0/docker-compose-`uname -s`-`uname -m` >  ~/docker-compose

Move to bin folder

 $ sudo mv ~/docker-compose /usr/local/bin/docker-compose

Make it executable

$ sudo chmod +x /usr/local/bin/docker-compose

Check

$ docker-compose --version
$ #docker-compose version 1.13.0, build 1719ceb

First hello world application

Create helloworld folder

mkdir ~/test && cd ~/test

Create example index.php file

echo "<?php echo 'Welcome to docker'; ?>" > index.php

Create Dockerfile

touch Dockerfile

Add following lines to Dockerfile

# Set base application image.
FROM php:7.0-cli

# Set working dir in container environment
WORKDIR /app

# Add all current files and folder on host env to container env
ADD . /app
# Alternative: COPY . /app

# Run command `php index.php` in container env
CMD ["php", "index.php"]
# Alternative: CMD php index.php

Build up image

docker build -t helloworld ./
# -t helloworld: set helloworld as image name
# ./ : context to build image. ./ mean current directory

Running container use helloworld image

docker run helloword

If you see "Welcome to docker" you're ready to go 😄


B. Basic usage cases

There are 3 ways to up and run a php application with docker. From hardest-low abstraction level to easiest-high abstraction level. I suggest to flow this order to understand more deeply and less confusing with last way. Let's dig into!

B.1. Up and running php application without docker-compose

Assume ~/project is project root folder

Step 1: Create 3 custom images with nginx, php-fpm, mysql

First create our docker folders

cd ~/project/docker && mkdir php-fpm mysql nginx

NOTE: Because limit folder access. So the context must be project root folder. Create php-fpm/Dockerfile with following lines

FROM php:fpm

# Project root context so we use .
ADD . /code

WORKDIR /code

CMD ["php-fpm"]

# Expose port for other services can use
EXPOSE 9000

Create nginx/Dockerfile with following lines

FROM nginx:alpine

WORKDIR /code

# Add our default site config
ADD ./docker/nginx/site.conf /etc/nginx/conf.d/

# Remove default nginx site
RUN rm /etc/nginx/conf.d/default.conf

CMD ["nginx"]

# Expose port for other services can use
EXPOSE 80 443

Create nginx/site.conf with content

 server {
      listen 80 default_server;
      server_name localhost;
      root /code;
      index index.php index.html index.htm;
      location / {
           try_files $uri $uri/ /index.php$is_args$args;
      }
      location ~ \.php$ {
          try_files $uri /index.php =404;
          // Some magical here :D. I'll explain later
          fastcgi_pass php-fpm:9000;
          fastcgi_index index.php;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          include fastcgi_params;
      }
    }

With mysql we dont need other configurations so we use base official mysql image is enough. We just start container use it in step 4

Step 2: Create network

Each container must in a same network to communicate with each others. So we create network

$ docker network create mynetwork

default use bridge driver. You can change it by using option --driver=<driver_name>

Step 3: Create share volume

We must define a volume which share code folder for nginx serve static content and php-fpm compile PHP script

$ docker volume create myvolume

Step 4: Startup 3 containers with share volume and joining network

docker run -v myvolume:/code --net mynetwork --name php-fpm php-fpm
docker run -v myvolume:/code --net mynetwork -p 8888:80 nginx
docker run --net mynetwork --name mysql mysql:latest

IMPORTANT NOTES:

  • Because containers are isolated from other so you must join containers into same network. Same as volume
  • p 8888:80 means forward port 80 in nginx container to port 8888 on host environment
  • Define --name argument give us 2 benefits
    • Manage container easily with name instead of long ID
    • Docker uses these names to find the IP of the services and containers for you. You can use hostnames in your code to provide abstraction that allows you to easily swap service containers or components. So you don't need/shouldn't to define static other service ip. Example is in nginx configuration above.

Test on: localhost:8888. You're up and runing. Cheer! But this is so long and has many boring repitive tasks. So docker-compose is here and save us 😄

B.2. Up and running php application with docker-compose

All we need is a nginx configuration file and docker-compose.yml file

Step 1: Create nginx configuration file

First create new folder

mkdir ~/project/docker-compose && cd ~/project/docker-compose

Create site.conf with flowing content

 server {
      listen 80 default_server;
      server_name localhost;
      root /code;
      index index.php index.html index.htm;
      location / {
           try_files $uri $uri/ /index.php$is_args$args;
      }
      location ~ \.php$ {
          try_files $uri /index.php =404;
          // Some magical here :D. I has explained..
          fastcgi_pass php-fpm:9000;
          fastcgi_index index.php;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          include fastcgi_params;
      }
    }

Step 2: Create docker-compose.yml

Create docker-compose.yml with content

version: '2'

services: 
  php:
    image: php:fpm
    volumes:
      - ..:/code
    networks:
      - mynetwork
  web:
    image: nginx:latest
    ports:
      - "8000:80"
    volumes:
      - ..:/code
      - ./site.conf:/etc/nginx/conf.d/default.conf
    networks:
      - mynetwork
  mysql:
    image: mysql:latest
    environment:
        - MYSQL_ROOT_PASSWORD=root
    networks:
      - mynetwork

networks:
  mynetwork:
    driver: bridge

Run docker-composer up and hurra! you get the same result as B.1. But this way is more clear and less painful. Right?

B.3. Up and running php application with laradock

Laradock is a full PHP development environment for Docker. If 2 ways above still make you uncomfortable or you want a more speedy way. Laradock is for you.

Step 1: Setup main laradock

Clone Laradock to anywhere you want. I assume ~/laradock

$ git clone https://github.com/Laradock/laradock.git ~/laradock

Enter the laradock folder and rename env-example to.env

$ cp ~/laradock/env-example ~/laradock/.env

Modify variable APPLICATION in ~/laradock/.env to folder contains all your projects. Assuming ~/code

APPLICATION=/home/user/code/

According to ~/laradock/docker-compose.yml. All folders and files in /home/user/code/ host env will be added to /var/www in container env

Step 2: Setup sites

Open ~/laradock/nginx/sites folder. There has 2 example site configs. You can create many sites as you want. Just copy example and change server_name , root variables which are suitable for each project Edit /etc/hosts on host env

127.0.0.1	localhost project1.dev project2.dev project3.dev

Inside laradock. Run

docker-compose up mysql nginx -d
# -d mean detach. You run docker in background mode

Test on project1.dev,..etc. That's it!


C. Appendices

C.1. Docker useful commands

C.1.1. docker image

  • List all image docker image ls
  • Remove a image docker image rm <image_id>
  • Build a image docker image build <context>
  • View info about a image docker image info <image_id>

C.1.2. docker container

  • Run a container from a local image or "on the cloud" image docker run [-d] [--net <network_name>] [-v <volume_name>:<mount_path>] [--name <wanted_container_name>] <image_id>
    • -d for detach mode
  • List container docker container ps [-a]
    • -a for including not running container
  • Remove a container docker container rm <container_id>
  • Stop, pause, start, restart container docker container stop | start | pause | restart <container_id>
  • Exec a command in container docker container exec <container_id> <command>
  • Use bash inside container docker container exec -it <container_id> /bin/bash

C.1.3. docker volume

  • List all volumes docker volume ls
  • Remove a volume docker volume rm <volume_name>
  • Create a volume docker volume create <volume_name> [-driver <driver_name>]
  • Inspect a volume docker volume inspect <volume_name>

C.1.3. docker network

  • List all networks docker network ls
  • Remove a network docker network rm <network_name>
  • Create a network docker network create <network_name> [-driver <driver_name>]
  • Inspect a network docker network inspect <network_name>
  • Connect/disconnect a container to/from a network docker network connect | disconnect <network_name> <container_id>

C.2. Docker-compose useful commands

  • Start all services docker-compose up [-d]
  • Start specific containers docker-compose up [-d] <service_1> <service_2> ...
  • Stop all services docker-compose down
  • List all containers running docker-compose images
  • Use bash inside container docker-compose exec <service_name> /bin/bash

Thanks for reading! 😄

Source: