Docker搭建自己的PHP开发环境


Posted in PHP onFebruary 24, 2018

1. 前言

1.1 为什么要用Docker ?

是否有这样的场景,你搞了一个项目,在本地开发时需要搭建环境,放到线上时也需要搭建环境,到公司想暗戳戳玩一下要搭建环境,不搭还不行,因为你的环境依赖还挺多。这个时候如果有了Docker,只需要在机器上装个Docker,放上写好的Dockerfile,一行命令就自动完成这个事,方便又高效,岂不是很爽?

1.2 准备

接下来,本文介绍如何搭建一个PHP的开发环境,将用 zPhal-dockerfiles 做为例子,这是我为我的博客系统准备的一套Dockerfile。

现在不管是Windows、Mac还是Linux,Docker都可以很好支持,包括Windows系统,在Win 10系统下Docker for Windows 其实还是挺不错的,就是比较吃内存。

通过Docker命令行,我们可以做很多事情,拉取镜像,运行容器,容器内执行命令等,但是现在,我们要用更加简单粗暴的方式,编写好Dockerfiles文件,然后通过docker-compose管理好这些文件,简化操作流程。

什么是Dockerfile?

Dockerfile是由一系列命令和参数构成的脚本,这些命令应用于拉取的基础镜像并最终创建一个新的镜像,通过Dockerfile我们可以创建一个你需要的镜像,里面是包含了你要安装的软件,相当于是提前定制好要安装的拓展,执行的命令等,然后一键执行,极大地简化操作流程。

按照本文来搭建环境,你需要:

首先了解一下Docker以及Docker的一些基本操作,还有docker-compose是什么。
然后需要安装Docker和docker-compose,我将使用docker-compose来管理我的Dockerfiles。
注意,编写Dockerfile是活的,不是死的,每个人写出来的Dockerfile都会不一样,取决于你的需求。

Docker的官方文档非常清楚,虽然是英文,但是基本上什么都有,有问题上文档翻是非常明智的: Docker Documentation 。

2. 开始编写

接下来都是以 zPhal-dockerfiles 为例子,完整的可以点链接进去看,下面的只是片段。

2.1 预览

首先,我们来看一下,我创建的这个Dockerfile项目,我大概分成了下面的目录(当然这个是自己定的,并不是要求这么去排版你的文件):

zPhal-dockerfiles
app/
 index.php
 phpinfo.php
data/
 .gitignore
files/
 mysql/
 conf.d/
  mysql-file.cnf
 Dockerfile
 nginx/
 conf.d/
  default.conf
  zphal.conf
 Dockerfile
 nginx.conf
 php/
 pkg/
  .gitignore
 Dockerfile
 php.ini
 php-dev.ini
 php-fpm.conf
 redis/
 Dockerfile
 docker-compose.yml
logs/
.gitgnore
README.md

在这个项目里,我用到PHP、MySQL、Nginx、Redis以及Composer、Phalcon拓展等。

总的来说,我们做这件事有三个流程:编写好各个软件的Dockerfile;编写好配置文件;通过docker-compose处理所有的Dockerfile,包括将配置配置文件扔进去Dockerfile文件将构建的镜像中。

2.2 编写Dockerfile文件

2.2.1 PHP

下面是PHP的Dockerfile:

FROM php:7.2-fpm

MAINTAINER goozp "gzp@goozp.com"
设置时区

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

更新安装依赖包和PHP核心拓展

RUN apt-get update && apt-get install -y \
git \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install zip \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install opcache \
&& docker-php-ext-install mysqli \
&& rm -r /var/lib/apt/lists/*

将预先下载好的拓展包从宿主机拷贝进去

COPY ./pkg/redis.tgz /home/redis.tgz
COPY ./pkg/cphalcon.tar.gz /home/cphalcon.tar.gz

安装 PECL 拓展,这里我们安装的是Redis

RUN pecl install /home/redis.tgz && echo "extension=redis.so" > /usr/local/etc/php/conf.d/redis.ini

安装第三方拓展,这里是 Phalcon 拓展

RUN cd /home \
&& tar -zxvf cphalcon.tar.gz \
&& mv cphalcon-* phalcon \
&& cd phalcon/build \
&& ./install \
&& echo "extension=phalcon.so" > /usr/local/etc/php/conf.d/phalcon.ini

安装 Composer

ENV COMPOSER_HOME /root/composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
ENV PATH $COMPOSER_HOME/vendor/bin:$PATH
RUN rm -f /home/redis.tgz \
rm -f /home/cphalcon.tar.gz 
WORKDIR /data
Write Permission
RUN usermod -u 1000 www-data

第一行定义了基础镜像,这里我们用了PHP 7.2的fpm版本,这里第二行定义了一个维护者。

接下来定义了时区,在每一个Dockerfile都定义了这一句,主要是为了使所有的容器的时间都与宿主机同步,其实我们可以在docker-composer.yml文件中这么定义:

services:

php-fpm:

volumes:

  - /etc/localtime:/etc/localtime:ro
但是在非Linux系统,比如Windows中运行时,我们不能取到/etc/localtime,为了更大兼容所有平台,我把时间同步写到Dockerfile中。

接下来安装一些拓展,其实安装拓展的过程类似于我们徒手在Linux中安装PHP拓展,值得一提的是Composer。我将Composer直接安装在了php-fpm的镜像中,其实官方也提供了Composer的镜像,拉取Composer镜像执行也可以达到目的,因为我们使用Composer只是为了执行Composer命令来管理我们的包,如果Composer单独是一个容器的话,我们在不用时,还可以将容器关掉;但是在这里,我直接将Composer装进php-fpm镜像中,主要是我的项目安装了一些PHP拓展,在编写composer.json文件时,我定义了extension的依赖,这样Composer执行时会检查环境是否安装了这些依赖,所有如果我直接用Composer镜像的话,还需要把我用的拓展安装到镜像里,就麻烦多了,所以我直接在PHP镜像中就把这个事做了,其实没什么区别,取决于你怎么用。

2.2.2 Nginx

下面是Nginx的Dockerfile:

FROM nginx:1.12
set timezome

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

这个就简单多了,我只设置了一个时间。因为我不需要安装其它的东西,可以直接使用官方的镜像。

当然,我们需要修改配置文件,只要事先写好配置文件就行,最后在 docker-compose.yml 文件中,将配置文件扔进去,这个下面会讲,包括PHP的配置文件,MySQL的配置文件,都是一样的。

2.2.3 MySQL

下面是 MySQL 的 Dockerfile:

FROM mysql:5.7
set timezome

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

MySQL也没有什么特别之处,直接使用官方的镜像。

2.2.4 Redis

下面是 Redis 的,也直接使用官方镜像:

FROM redis:3.2
set timezome

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

2.3 编写配置文件

如何处理配置文件呢,我将配置文件进行归类,PHP的配置文件放在PHP目录下,Nginx的配置放在Nginx目录下,至于要不要再新建一个子文件夹就看情况了,比如conf.d文件夹。

下面以Nginx配置文件为例,首先Nginx目录是这样的:

nginx/

conf.d/

    default.conf

    zphal.conf

Dockerfile

nginx.conf

除了nginx.conf外,还有一个子文件夹conf.d用来存放所有的域名配置文件,在Linux下搭建过PHP环境的应该都比较熟悉。这些配置文件就是我们到时候要传进去容器中的文件,我们并不会在宿主机使用这些文件。

所以需要注意的最重要一点就是,配置文件中出现的路径是容器内环境的路径,而不是宿主机的路径,每一个容器内都有一个运行环境,都是一台微型小系统,这些路径都是容器内的路径。我们可以通过挂载与容器内通讯来同步文件,在命令行启动容器也需要挂载文件路径,而现在挂载这一步我们也用docker-compose来解决。

下面是一个配置文件示例:

server {
listen 80 default;
index index.html index.htm;
server_name localhost docker;

root /data/www;
index index.php index.html index.htm;
location / {
 try_files $uri $uri/ /index.html;
}

location ~ \.php {
 include fastcgi_params;
 fastcgi_pass php-fpm:9000;
 fastcgi_index index.php;
 fastcgi_param SCRIPT_FILENAME /data/www/$fastcgi_script_name;
}
}

而root /data/www中,/data/www路径,是到时候Nginx容器的路径,而不是当前在操作的宿主机的路径,所以到时候我们要挂载Web程序放的位置到这个路径。

2.4 编写 docker-compose.yml

在PHP、Nginx等目录的同级,我们创建一个docker-compose.yml,我们在执行docker-compose相关命令时,会自动找到这个文件,并根据里面的内容来执行。

接上面Nginx的例子,我们先谈挂载,因为这是最重要的一步。在docker-compose.yml中,Nginx的部分:

build: ./nginx
depends_on:
 - php-fpm
links:
 - php-fpm:php-fpm
volumes:
 - ../app:/data/www:rw
 - ./nginx/conf.d:/etc/nginx/conf.d:ro
 - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
 - ../logs/nginx:/var/log/nginx
ports:
 - "80:80"
 - "8080:8080"
 - "443:443"
restart: always
command: nginx -g 'daemon off;'

有一个volumes参数,这里就是我们要挂载的目录的相关配置,第一条我们将../app挂载到/data/www之中,也是我们配置文件中定义的默认监听的root,而APP目录是我们宿主机中的一个目录,通过这样挂载我们可以直接将我们的项目文件放到APP中,Docker会帮你传输到容器内的/data/www目录下。

其它的参数:

build定义了你的Dockerfile在哪里,如果没有写Dockerfile可以不用build,可以用images参数定义官方镜像,比如image:mysql:5.7;
depends_on表示将依赖其它镜像,比如Nginx依赖php-fpm,没有它我Nginx没法玩;
links定义连接,比如要连接到php-fpm容器,就是php-fpm:php-fpm,后面是别名;
ports表示端口映射,80:80表示将80端口映射到宿主机的80端口;
restart重启,restart: always表示将自动重启;
command是自动执行的命令;
……
参数很多,更多的可以参考官方文档。

下面是一个完整的 docker-compose.yml 文件:

version: '3.2'
services:
php-fpm:
build: ./php/
ports:
 - "9000:9000"
links:
 - mysql-db:mysql-db
 - redis-db:redis-db
volumes:
 - ../app:/data/www:rw
 - ./php/php-dev.ini:/usr/local/etc/php/php.ini:ro
 - ./php/php-fpm.conf:/usr/local/etc/php-fpm.conf:ro
 - ../logs/php-fpm:/var/log/php-fpm:rw
restart: always
command: php-fpm

nginx:
build: ./nginx
depends_on:
 - php-fpm
links:
 - php-fpm:php-fpm
volumes:
 - ../app:/data/www:rw
 - ./nginx/conf.d:/etc/nginx/conf.d:ro
 - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
 - ../logs/nginx:/var/log/nginx
ports:
 - "80:80"
 - "8080:8080"
 - "443:443"
restart: always
command: nginx -g 'daemon off;'

mysql-db:
 build: ./mysql
 ports:
 - "3306:3306"
 volumes:
 - ../data/mysql:/var/lib/mysql:rw
 - ../logs/mysql:/var/lib/mysql-logs:rw
 - ./mysql/conf.d:/etc/mysql/conf.d:ro
 environment:
 MYSQL_ROOT_PASSWORD: 123456
 MYSQL_DATABASE: zphaldb
 MYSQL_USER: zphal
 MYSQL_PASSWORD: zphal123
 restart: always
 command: "--character-set-server=utf8"
redis-db:
 build: ./redis
 ports:
 - "6379:6379"
 volumes:
 - ../data/redis:/data
 restart: always

3. 使用

这一套编写下来,我们怎么用呢?

3.1 使用搭建好的环境

首先,进入项目Dockerfiles的目录下,这里是files目录:

cd zPhal-dockerfiles/files

wget https://pecl.php.net/get/redis-3.1.6.tgz -O php/pkg/redis.tgz

wget https://codeload.github.com/phalcon/cphalcon/tar.gz/v3.3.1 -O php/pkg/cphalcon.tar.gz
然后下载我们会用到的PHP拓展包。

执行命令:

docker-compose up
Docker会自动通过编写好的docker-compose.yml内容构建镜像,并且启动容器。

如果没问题,下次启动时可以以守护模式启用,所有容器将后台运行:

docker-compose up -d
关闭容器:

可以这样关闭容器并删除服务:

docker-compose down
使用 docker-compose 基本上就这么简单,用stop,start等这些命令来操纵容器服务。而更多的工作是在于编写Dockerfile和docker-compose.yml文件。

3.2 使用Composer

当我们要使用Composer时怎么做呢? 我们已经在php-fpm里安装了Composer。

用docker-compose进行操作:

docker-compose run --rm -w /data/www/zPhal php-fpm composer update
-w /data/www/zPhal为在php-fpm的工作区域,zPhal项目也是挂载在里面,所有我们可以直接在容器里运行Composer。

或者进入宿主机APP目录下用Docker命令:

cd zPhal-dockerfiles/app

docker run -it --rm -v `pwd`:/data/www/ -w /data/www/zPhal files_php-fpm composer update

4. 注意事项

注意挂载路径。
构建失败时,注意容器内是否报错。
加速镜像。如果过程下载镜像很慢,可以使用国内的加速镜像服务。

PHP 相关文章推荐
php park、unpark、ord 函数使用方法(二进制流接口应用实例)
Oct 19 PHP
php入门学习知识点一 PHP与MYSql连接与查询
Jul 14 PHP
PHP中创建图像并绘制文字的例子
Nov 19 PHP
实现PHP框架系列文章(6)mysql数据库方法
Mar 04 PHP
Laravel实现构造函数自动依赖注入的方法
Mar 16 PHP
Thinkphp批量更新数据的方法汇总
Jun 29 PHP
PHP实现批量检测网站是否能够正常打开的方法
Aug 23 PHP
PHP实现链式操作的原理详解
Sep 16 PHP
解决form中action属性后面?传递参数 获取不到的问题
Jul 21 PHP
PHP封装cURL工具类与应用示例
Jul 01 PHP
Laravel框架实现抢红包功能示例
Oct 31 PHP
laravel使用redis队列实例讲解
Mar 23 PHP
php爬取天猫和淘宝商品数据
Feb 23 #PHP
Laravel如何使用Redis共享Session
Feb 23 #PHP
Laravel 实现密码重置功能
Feb 23 #PHP
支持汉转拼和拼音分词的PHP中文工具类ChineseUtil
Feb 23 #PHP
对于Laravel 5.5核心架构的深入理解
Feb 22 #PHP
php 广告点击统计代码(php+mysql)
Feb 21 #PHP
详细解读php的命名空间(二)
Feb 21 #PHP
You might like
PHP Array交叉表实现代码
2010/08/05 PHP
php多层数组与对象的转换实例代码
2013/08/05 PHP
PHP封装的一个支持HTML、JS、PHP重定向的多功能跳转函数
2014/06/19 PHP
php检索或者复制远程文件的方法
2015/03/13 PHP
yii权限控制的方法(三种方法)
2015/12/28 PHP
PHP命名空间和自动加载类
2016/04/03 PHP
PHP实现登录搜狐广告获取广告联盟数据的方法【附demo源码】
2016/10/14 PHP
thinkphp实现把数据库中的列的值存到下拉框中的方法
2017/01/20 PHP
php工具型代码之印章抠图
2018/07/18 PHP
js 静态动态成员 and 信息的封装和隐藏
2011/05/29 Javascript
40款非常有用的 jQuery 插件推荐(系列一)
2011/12/21 Javascript
jQuery LigerUI 使用教程入门篇
2012/01/18 Javascript
Ajax搜索结果页面下方的分页按钮的生成
2012/04/05 Javascript
浅谈jQuery事件绑定原理
2015/01/02 Javascript
javascript实现行拖动的方法
2015/05/27 Javascript
node.js操作mysql(增删改查)
2015/07/24 Javascript
Javascript页面跳转常见实现方式汇总
2015/11/28 Javascript
用NODE.JS中的流编写工具是要注意的事项
2016/03/01 Javascript
用AngularJS的指令实现tabs切换效果
2016/08/31 Javascript
JS实现隔行换色的表格排序
2017/03/27 Javascript
vue js秒转天数小时分钟秒的实例代码
2018/08/08 Javascript
浅谈Vue数据响应
2018/11/05 Javascript
[01:35:53]完美世界DOTA2联赛PWL S3 Magma vs GXR 第二场 12.13
2020/12/17 DOTA
Python实现获取系统临时目录及临时文件的方法示例
2019/06/26 Python
Python 把序列转换为元组的函数tuple方法
2019/06/27 Python
python日志模块logbook使用方法
2019/09/19 Python
关于python pycharm中输出的内容不全的解决办法
2020/01/10 Python
pytorch三层全连接层实现手写字母识别方式
2020/01/14 Python
Python基于类路径字符串获取静态属性
2020/03/12 Python
挪威户外活动服装和装备购物网站:Bergfreunde挪威
2016/10/20 全球购物
Spartoo瑞典:鞋子、包包和衣服
2018/09/15 全球购物
Shop Apotheke瑞士:您的健康与美容网上商店
2019/10/09 全球购物
工程造价专业大学生职业规划范文
2014/03/09 职场文书
关于祖国的演讲稿
2014/05/04 职场文书
2019暑假学生安全口号
2019/06/27 职场文书
Java中PriorityQueue实现最小堆和最大堆的用法
2021/06/27 Java/Android