🦄 2024 独立开发者训练营,一起创业!查看介绍 / 立即报名(剩余10个优惠名额) →

Docker Compose:创建本地开发 PHP 应用的环境

Docker Compose 可以让我们用一个文件来描述应用需要的环境,在上面定义应用需要的各种服务,比如 web,数据库,脚本解释,缓存等等,我们也可以配置服务需要的网络与数据卷。这篇文章会创建一个运行 PHP 应用的环境,代码可以在 github 上找到,参考宁皓网课程《Docker:容器化应用》。

定义了下面这些服务:

  • db:使用 mariadb 作为应用的数据库
  • php:解释 php 脚本,使用 php-fpm
  • web:使用 NGINX 作为应用的 web 服务器
  • console:常用工具
  • redis:缓存
  • phpmyadmin:管理数据库的 web 界面

准备

在本地安装 Docker for Mac 或 Docker for Windows ,然后启动 docker ,再打开你喜爱的文本编辑器与命令行工具。为项目创建一个目录,在根目录下创建一个 docker-compose.yml 文件。

cd ~/desktop
mkdir nest
cd nest
atom docker-compose.yml

目录结构

你可以按自己的喜好组织项目,下面是我用的方法,app 目录放的是应用的代码,services 下面是创建服务需要用的东西,有些服务需要我们自己去创建镜像,在一个 Dockerfile 文件里说明一下你想要的镜像是什么样的。在创建自定义镜像的时候也可能需要用到一些额外的文件,比如一些配置文件,一般在创建镜像的时候会把这些配置文件复制到镜像里。

├── app
│   ├── index.html
│   └── phpinfo.php
├── docker-compose.yml
└── services
    ├── console
    │   ├── Dockerfile
    │   └── composer-1.1.3.phar
    ├── web
    │   └── config
    │       └── default.conf
    └── php
        ├── Dockerfile
        └── config
            ├── opcache-recommended.ini
            └── php.ini

Compose 文件

Compose 文件用的是 yaml 格式,文件一开始说明一下使用的 compose 版本,这个文件分成三个主要部分,services(服务),networks(网络),volumes (数据卷)。在这三部分主要的部分下面列出你需要的东西,docker 会自动为 compose 的项目创建一个默认的网络,你可以添加自己想要的网络,然后分配给指定的服务使用。在 volumes 下可以列出创建的数据卷,在定义服务的时候,你可以指定使用的数据卷,还有它对应的要挂载到的容器里的位置。

version: '2'

services:
# 定义的服务

networks:
# 服务需要用的网络

volumes:
# 服务需要用的数据卷

db

db 是数据服务,我打算用 mariadb ,先去给这个服务添加一个数据卷,然后在这个服务里使用一下这个数据卷,把数据库生成的数据放到这里,这样即使我们删除容器,数据服务里的数据也会保留在主机上,下回创建容器的时候,可以继续使用已有的数据。在 volumes 下面,添加一个名字是 db 的数据卷:

services:
# ...

volumes:
 db:
   driver: local

然后再去添加一个名字是 db 的服务,指定一下服务使用的镜像,这个镜像我们也可以用自己创建的 Dockerfile 去创建一下,或者你不打算定制镜像,也可以直接使用现成的,这里我用了 mariadb:10.1 这个镜像。注意最好设置具体要使用的版本。

这个镜像里有一些环境变量,我们可以在定义服务的时候去设置一下它们的值,这里我设置了 root 用户的密码,要创建的数据库,用户名,还有密码,你可以 根据自己的需求去修改这些变量的值。

在 db 服务上我用了 volumes  为它指定了一个 db 数据卷,挂载的位置是容器的 /var/lib/mysql ,这是存储数据库的默认的地方。

services:
  db:
    image: mariadb:10.1
    environment:
      MYSQL_ROOT_PASSWORD: "root"
      MYSQL_DATABASE: "app"
      MYSQL_USER: "app"
      MYSQL_PASSWORD: "123123"
    volumes:
      - db:/var/lib/mysql

测试

在我们的 compose 文件里现在已经有了一个叫 db 的服务,在命令行工具下,进入到项目所在的目录,执行:

docker-compose up -d

Docker 会按照 compose 文件里的指示,创建应用需要的网络,数据卷,还有一个名字是 db 的数据服务,你可以进入到这个服务容器里看一下:

docker-compose exec db bash

进入以后使用 mysql 登录到数据库检查一下:

mysql -u root -p
# 输入 root 用户密码,密码是在定义 db 服务的时候使用 MYSQL_ROOT_PASSWORD 变量设置的。

show databases;
# 你能看到一个名字是 app 的数据库,这个数据库是在定义 db 服务的时候使用 MYSQL_DATABASE 变量设置的。

php

定义一个解释 php 的服务,我打算自己创建这个服务用的镜像,所以用了 build,而不是 image 。我告诉了  docker 自己要创建的这个镜像要使用的那个 Dockerfile 文件的位置(./services/php/Dockerfile)。

volumes 下面是服务用的数据卷,我把 compose 文件所在目录下的 app 这个目录,挂载到了容器的 /mnt/app 这个位置上了。这样我可以直接修改项目的代码,然后立即看到结果。

services:
  db:
  # ...
  php:
    build:
      context: ./services/php
      dockerfile: Dockerfile
    volumes:
      - ./app:/mnt/app

Dockerfile

构建 php 服务的镜像用的 Dockerfile 是 ./services/php/Dockerfile。FROM,设置了一下这个镜像要基于哪个镜像去创建,我用了 php:7.0-fpm 这个官方提供的镜像。MAINTAINER 设置了一下维护的作者。RUN 了几行命令,主要是去安装一些软件包,比如一些额外的 php 扩展。在安装这些扩展之前 ,你需要先安装扩展依赖的一些其它的软件包,这里的 libpng12-dev,libjpeg-dev,就是 gd 这个 php 扩展需要的软件包。然后我又 COPY 了两个配置文件到镜像里面。

FROM php:7.0-fpm
MAINTAINER wanghao <wanghao@ninghao.net>

RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev \
  && rm -rf /var/lib/apt/lists/* \
  && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \
  && docker-php-ext-install gd mysqli pdo_mysql zip opcache

COPY ./config/php.ini /usr/local/etc/php/conf.d/
COPY ./config/opcache-recommended.ini /usr/local/etc/php/conf.d/

php.ini

自定义的 php 配置。把你想要的 php 配置放到这个文件里,重新 build 镜像,然后再次启动 php 服务的时候会用新的镜像重新创建容器,这样配置就会生效了。

memory_limit = 256M
post_max_size = 100M
upload_max_filesize = 100M

opcache-recommended.ini

这是 php 的 opcache 扩展的配置文件。我发现在开发 WordPress 的时候,如果使用了 opcache ,修改代码以后并不能立即看到结果。所以在开发阶段可以关掉 opcache 功能,在生产环境打开它,会提高代码的执行效率。

opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
# 关闭 opcache 可以让 opcache.enable=0,打开 opcache 可以让 opcache.enable=1
# 在开发应用的时候可以关掉 opcache 。
opcache.enable=0

web

web 服务我打算用 nginx ,用 image 指定了要使用的镜像,ports 设置了发布的端口号,让主机上的 8080 对应容器的 80 。depends_on 可以设置服务的依赖,我让 web 服务依赖之前定义的 php ,这样会先启动 php ,然后再启动 web 。volumes_from 设置了让这个 web 服务使用在 php 服务里定义的数据卷。

另外我又用了 volumes 自己设置了一个数据卷,让主机上包含 nginx 配置文件的目录,对应容器里的 nginx 服务的配置文件目录,这样我就可以直接在本机上修改 nginx 的配置文件,然后重新启动一下 web 服务,配置就可以生效了。这种方法适用在开发环境上,因为修改了配置不需要重新 build 镜像,在生产环境中,你需要自己 build 这个 nginx 镜像,把想要的配置直接复制到镜像里去。

services:
  db:
  # ...
  php:
  # ...
  web:
    image: nginx:1.11.1
    ports:
      - "8080:80"
    depends_on:
      - php
    volumes_from:
      - php
    volumes:
      - ./images/nginx/config:/etc/nginx/conf.d

default.conf

./images/nginx/config/default.conf,这是一个最基本的 nginx 配置文件,设置了应用的 root 是在 /mnt/app 这里,我们已经把主机上的 app 目录挂载到了容器里的 /mnt/app 这个地方。

注意这条指令:fastcgi_pass php:9000;  这里的 php 是我们定义的 php 这个服务的名字,在 compose 文件里定义的服务,它们之间可以使用服务的名字相互进行沟通,因为 docker 内置了 DNS 功能。

server {
  listen       80;
  server_name  localhost;
  root          /mnt/app;
  index         index.php index.html index.htm;

  location / {
     try_files $uri $uri/ /index.php?$query_string;
   }

  location ~ \.php$ {
     fastcgi_pass   php:9000;
     fastcgi_index  index.php;
     fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
     include        fastcgi_params;
  }
}

console

在开发应用的时候还会用到一些小工具,比如 php 包管理 composer ,WordPress 项目管理 wp-cli,Drupal 项目管理 drush 等等。我想把它们单独放到一个服务里面,这个服务用的镜像我需要自己去创建。

services:
  db:
  # ...
  php:
  # ...
  web:
  # ...  
  console:
    build:
      context: ./images/console
      dockerfile: Dockerfile
    volumes_from:
      - php
    tty: true

Dockerfile

./services/console/Dockerfile,这是为 console 服务准备的创建镜像用的文件。

FROM php:7.0
MAINTAINER wanghao <wanghao@ninghao.net>

WORKDIR /mnt/app

# 常用工具
RUN apt-get update && apt-get install -y git curl wget cron vim locales libfreetype6-dev mariadb-client \
  && rm -rf /var/lib/apt/lists/* \
  && pecl install zip \
  && docker-php-ext-enable zip \
  && docker-php-ext-install mysqli pdo_mysql

# 把语言设置成简体中文
RUN dpkg-reconfigure locales && \
  locale-gen C.UTF-8 && \
  /usr/sbin/update-locale LANG=C.UTF-8
RUN echo 'zh_CN.UTF-8 UTF-8' >> /etc/locale.gen && \
  locale-gen
ENV LC_ALL C.UTF-8
ENV LANG zh_CN.UTF-8
ENV LANGUAGE zh_CN.UTF-8

# 配置 git
RUN git config --global user.name "wanghao8080" \
  && git config --global user.email "117663444@qq.com"

# 安装与配置 composer
# Composer 官方安装脚本 https://getcomposer.org/download/
# 因为在国内下载 composer 太慢,我们直接把下载下来的 composer 复制到容器里使用。
COPY ./composer-1.1.3.phar /usr/local/bin/composer
RUN chmod +x /usr/local/bin/composer
RUN echo 'export PATH="$PATH:$HOME/.composer/vendor/bin"' >> ~/.bashrc \
  && . ~/.bashrc \
  && composer config -g repo.packagist composer https://packagist.phpcomposer.com

# 安装管理 WordPress 项目的 wp-cli
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
  && chmod +x wp-cli.phar \
  && mv wp-cli.phar /usr/local/bin/wp

# 安装管理 Drupal 项目的 drush
RUN php -r "readfile('http://files.drush.org/drush.phar');" > drush \
  && chmod +x drush \
  && mv drush /usr/local/bin


redis

redis 服务可以当缓存用,WordPress,Drupal,Laravel 这些应用都会用到它。定义这个服务只是简单的用了一个官方的镜像,设置了一个公布的端口号。

services:
  db:
  # ...
  php:
  # ...
  web:
  # ...  
  console:
  # ...
  redis:
    image: redis:3.2.1
    ports:
      - "6379:6379"

phpredis

我打算配置 Drupal 8 使用 Redis,除了需要 redis 服务以外,我还需要安装 phpredis 这个 php 扩展。打开创建 php 服务镜像用的 Dockerfile(./services/php/Dockerfile),下面这段代码是去下载指定版本的 phpredis 扩展:

ENV PHPREDIS_VERSION 3.0.0
RUN curl -L -o /tmp/redis.tar.gz https://github.com/phpredis/phpredis/archive/$PHPREDIS_VERSION.tar.gz \
 && tar xfz /tmp/redis.tar.gz \
 && rm -r /tmp/redis.tar.gz \
 && mv phpredis-$PHPREDIS_VERSION /usr/src/php/ext/redis

然后再用 php 官方镜像里的 docker-php-ext-install 这个脚本去安装 php 扩展:

  && docker-php-ext-install ... redis

现在这个 Dockerfile 看起来是这样的:

FROM php:7.0-fpm
MAINTAINER wanghao <wanghao@ninghao.net>

ENV PHPREDIS_VERSION 3.0.0
RUN curl -L -o /tmp/redis.tar.gz https://github.com/phpredis/phpredis/archive/$PHPREDIS_VERSION.tar.gz \
 && tar xfz /tmp/redis.tar.gz \
 && rm -r /tmp/redis.tar.gz \
 && mv phpredis-$PHPREDIS_VERSION /usr/src/php/ext/redis

RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev \
 && rm -rf /var/lib/apt/lists/* \
 && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \
 && docker-php-ext-install gd mysqli pdo_mysql zip opcache redis


COPY ./config/php.ini /usr/local/etc/php/conf.d/
COPY ./config/opcache-recommended.ini /usr/local/etc/php/conf.d/

phpmyadmin

phpmyadmin 是管理 mysql 或 mariadb 数据库的 web 界面。这个服务用了 phpmyadmin/phpmyadmin 这个镜像,发布了端口,让主机的 8081 对应容器的 80 端口,这样访问 主机:8081 的时候就会打开这个管理数据库的 web 界面,镜像里设置了一些环境变量,在这个服务里用了一些,主要是告诉 phpmyadmin 数据库的主机是谁,管理数据库使用的用户是谁,密码是什么。

services:
  db:
  # ...
  php:
  # ...
  web:
  # ...  
  console:
  # ...
  redis:
  # ...
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    ports:
      - "8081:80"
    environment:
      PMA_HOST: "db"
      PMA_USER: "root"
      PMA_PASSWORD: "root"

启动

在 app 目录下创建一个 phpinfo.php ,内容是:

<?php phpinfo(); ?>

进入到项目下面,执行:

docker-compose up -d

打开浏览器,访问:

http://localhost:8080/phpinfo.php

你会看到 php 信息页面。

Docker

评论

剪藏到印象笔记里!

赞!nest 仓库越来越完善了!期待 docker swarm 教程和开发→测试→生产环境流程的视频教程~~

我觉得是不是应该把 nginx 与 php 放到一个容器里,然后再做一个 nginx 用来当代理,这样在一个集群里就可以同时运行多个在 80 端口的网站了。

我看了一下官方对于 Docker-compose 的最佳实践,里面写到建议每个服务都单独运行在一个容器中……所以其实我觉得,php 应该跟 nginx 分开……这样就只需要一个 nginx 就好了,也可以做负载均衡

嗯,再研究吧,先用用再说。

皓哥,Redis 直接安装了就能用?

是啊,我是按 Drupal 8 的要求配置的,我在 Drupal 8 上试了,可以用。

Windows 下无法构建 Console 镜像……

RUN apt-get update && apt-get install -y git curl wget cron vim libfreetype6-dev mariadb-client \
&& rm -rf /var/lib/apt/lists/* \
&& yum reinstall glibc-common -y \
&& pecl install zip \
&& docker-php-ext-enable zip \
&& docker-php-ext-install mysqli pdo_mysql

这段没法执行……貌似是 yum reinstall glibc-common -y 这段的问题

收到!

修改了一下设置容器支持的语言的方法,之前整错了。

git clone 下来, 然后
docker-compose build
提示要
db uses an image, skipping
Building php
ERROR: Couldn't connect to Docker daemon - you might need to run `docker-machine start default`.

但是我的 default 是启动运行的,已经运行过了docker-machine start default 这是咋回事呢?

通过命令 eval "$(docker-machine env default)" 解决了这个问题。

docker-machine ls 之后空空如也,default不见了,但是docker还好好的在运行是啥意思?

因为你安装了 docker for windows 。之前的 docker toolbox 里面在本机创建了一个默认的 machine(虚拟机),docker for windows 不再使用 virtualbox 虚拟机了。

楼主, 这个帮了我很大的忙, 目前您这个docker lnmp方案是我见过文件夹结构最合理的, 不过目前有一些问题向您反映 , 就是docker 官方默认的php-fpm, php-cli 镜像从 jessie 变成了stretch , 所以导致编译的时候偶尔会失败, 把dockerfile 里面的镜像源加上后缀就好了。

谢谢啊,这个项目只是为了在课程里介绍一下 Docker Compose 的使用方法。具体用的话,你还得自己根据实际情况改改:)

微信好友

用微信扫描二维码,
加我好友。

微信公众号

用微信扫描二维码,
订阅宁皓网公众号。

240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

15260
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点