在写这篇博客的时候,系统已经稳定运行在docker容器上。本着折腾的精神,觉的还是应该将博客进行docker化,一来毕竟在内存上多花了不少钱,不充分利用一下有点说不不过去,最重要的是如果docker化,以后万一需要迁移博客到别的主机上,我只需要将相关容器打包一下拷贝过去起来就行了,简单快速。
这也是我第一次实践docker化工作,从小心谨慎以及可回退角度,做了以下目标设定:
-
将现有的所有服务docker化,包含 nginx,php-fpm,memcached,mysql
-
docker化工作不能影响现有底层系统上的已有lnmp服务的配置,因为现有配置中有不少配置是会定期动态变化的,所以不能因为docker化改变现有的结构、配置等。同时要做到万一不用docker直接本地启动原来服务即可。 (这个需求其实埋下很多坑)
-
docker化后ssllabs评分不能降低,必须还是A+
为了实现上述目标,直接pull一个现成的官方 nginx,官方php,甚至官方wordpress是不可行的,需要创建一个自己的容器来实现ct-transparency,brotli, dynamic-tls-record-sizing,以及openssl需要打上chacha20的patch。而为了简化工作,决定将php和nginx放到一个容器里。
[title]Nginx-php容器化[/title]
在PHP官方的5.6.31-fpm-alphine镜像基础上补充编译nginx,并为php增加memached扩展支持。以下dockerfile在docker hub和阿里云docker镜像站上以及本地均build通过。在实际dockerfile制作过程遇到了不少问题,总结下来主要有:
- 为了兼容原来nginx配置环境的,需要仔细考虑编译过程中相关配置目录的规划以及相关目录的提前创建,防止在mount目录给容器后,导致原来的配置提示找不到相关引用文件,在这一点上经历了多次修改和重编译,踩了不少坑
- centos 7.3上如果docker的storage driver是 overlay方式,且linux本身是xfs格式系统+ftype=1,则可能会出现build时候无法rm -rf命令无法删除目录情况,这是由于centos的bug,参考https://github.com/docker/docker/issues/31717
我是将docker启动的storage driver改为devicemapper方式来解决 - 其它因素,参考了不少别人的dockerfile,certificate transparency编译用的包的下载地址引用的是github上一个人的,但这个人突然有一天删除了那个文件,上一次build还通过了,下次build就因为无法下载失败了。。还好我有提前本地备份,索性将那几个需要的文件都改为在自己的github上提供
- 国内build有可能还会遇到dns无法解析问题,其实明明是正常的,比如在阿里云国内节点编译则会失败,选择阿里云境外节点则顺利完成
- 由于是nginx+php在同一个容器里需要同时暴露服务,这与容器本来的设计思想有些违背,所以使用了supervisord来实现前台启动双服务,需要注意的是supervisord里一样不能对nginx以daemon方式启动,否则supervisor会不断的提示nginx启动失败并重启
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
FROM php:5.6.31-fpm-alpine #FROM alpine:3.6 MAINTAINER Jing Lin <web@myf5.net> ENV NGINX_VERSION 1.12.1 ENV OPENSSL_VERSION 1.0.2l ENV OPENSSL_CHACHA https://raw.githubusercontent.com/cloudflare/sslconfig/master/patches/openssl__chacha20_poly1305_draft_and_rfc_ossl102j.patch ENV NGX_VERSION_TRANSPARENCY=1.3.2 ENV NGX_VERSION_BROTLI=1.0.2 ENV php_conf /usr/local/etc/php-fpm.conf ENV fpm_conf /usr/local/etc/php-fpm.d/www.conf ENV php_vars /usr/local/etc/php/conf.d/docker-vars.ini ################################### # OPENSSL # ################################### RUN apk add --no-cache --virtual .build-deps \ gcc \ libc-dev \ make \ pcre-dev \ zlib-dev \ linux-headers \ curl \ gnupg \ libxslt-dev \ gd-dev \ geoip-dev \ perl-dev \ wget \ ca-certificates \ g++ \ zip \ && mkdir -p /usr/src \ && wget https://openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz \ && tar zxvf openssl-$OPENSSL_VERSION.tar.gz -C /usr/src \ && rm openssl-$OPENSSL_VERSION.tar.gz \ && wget -O /usr/src/chacha.patch $OPENSSL_CHACHA \ && cd /usr/src/openssl-$OPENSSL_VERSION \ && patch -p1 < ../chacha.patch \ && ./config \ && make depend ################################### # CERTIFICATE TRANSPARENCY # ################################### COPY nginx-ct-1.3.2.zip /tmp/ RUN unzip /tmp/nginx-ct-1.3.2.zip -d /usr/src \ && rm /tmp/nginx-ct-1.3.2.zip \ && echo "CT copyed and unzipped" ################################### # GOOGLE BROTLI # ################################### COPY ngx_brotli-1.0.2.zip /tmp/ RUN unzip /tmp/ngx_brotli-1.0.2.zip -d /usr/src \ && rm /tmp/ngx_brotli-1.0.2.zip \ && echo "BROTILI copyed and unzipped" ################################### # TLS Dynamic Reord Sizing # # Patch for NGINX # ################################### RUN wget -O /usr/src/nginx-dynamic_tls.patch https://raw.githubusercontent.com/cloudflare/sslconfig/master/patches/nginx__1.11.5_dynamic_tls_records.patch \ && echo "TLS Dynamic Reord Sizing patch downloaded" ################################### # NGINX # ################################### ENV GPG_KEYS B0F4253373F8F6F510D42178520A9993A1C052F8 ENV CONFIG "\ --prefix=/usr/local/nginx \ --sbin-path=/usr/sbin/nginx \ --modules-path=/usr/lib/nginx/modules \ --conf-path=/usr/local/nginx/conf/nginx.conf \ --error-log-path=/var/log/nginx/error.log \ --http-log-path=/var/log/nginx/access.log \ --pid-path=/var/run/nginx.pid \ --lock-path=/var/run/nginx.lock \ --http-client-body-temp-path=/var/cache/nginx/client_temp \ --http-proxy-temp-path=/var/cache/nginx/proxy_temp \ --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \ --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \ --http-scgi-temp-path=/var/cache/nginx/scgi_temp \ --user=www \ --group=www \ --with-http_ssl_module \ --with-http_realip_module \ --with-http_addition_module \ --with-http_sub_module \ --with-http_dav_module \ --with-http_flv_module \ --with-http_mp4_module \ --with-http_gunzip_module \ --with-http_gzip_static_module \ --with-http_random_index_module \ --with-http_secure_link_module \ --with-http_stub_status_module \ --with-http_auth_request_module \ --with-http_xslt_module=dynamic \ --with-http_image_filter_module=dynamic \ --with-http_geoip_module=dynamic \ --with-http_perl_module=dynamic \ --with-threads \ --with-stream \ --with-stream_ssl_module \ --with-http_slice_module \ --with-mail \ --with-mail_ssl_module \ --with-stream_ssl_preread_module \ --with-stream_realip_module \ --with-stream_geoip_module=dynamic \ --with-file-aio \ --with-http_v2_module \ --with-compat \ --with-openssl=/usr/src/openssl-$OPENSSL_VERSION \ --add-module=/usr/src/nginx-ct-${NGX_VERSION_TRANSPARENCY} \ --add-module=/usr/src/ngx_brotli-${NGX_VERSION_BROTLI} \ " RUN \ addgroup -S www \ && adduser -D -S -h /var/cache/www -s /sbin/nologin -G www www \ && curl -fSL http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz -o nginx.tar.gz \ && curl -fSL http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz.asc -o nginx.tar.gz.asc \ && export GNUPGHOME="$(mktemp -d)" \ && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$GPG_KEYS" \ && gpg --batch --verify nginx.tar.gz.asc nginx.tar.gz \ && rm -r "$GNUPGHOME" nginx.tar.gz.asc \ && tar -zxC /usr/src -f nginx.tar.gz \ && rm nginx.tar.gz \ && cd /usr/src/nginx-$NGINX_VERSION \ && patch -p1 < /usr/src/nginx-dynamic_tls.patch \ && echo "Dynamic TLS size recording pathed for nginx" \ && ./configure $CONFIG --with-debug \ && make \ && mv objs/nginx objs/nginx-debug \ && mv objs/ngx_http_xslt_filter_module.so objs/ngx_http_xslt_filter_module-debug.so \ && mv objs/ngx_http_image_filter_module.so objs/ngx_http_image_filter_module-debug.so \ && mv objs/ngx_http_geoip_module.so objs/ngx_http_geoip_module-debug.so \ && mv objs/ngx_http_perl_module.so objs/ngx_http_perl_module-debug.so \ && ./configure $CONFIG \ && make \ && make install \ && rm -rf /usr/local/nginx/html/ \ && mkdir /usr/local/nginx/vhost/ \ && mkdir /usr/local/nginx/ssl/ \ && mkdir -p /var/cache/nginx/client_temp \ && mkdir -p /var/cache/nginx/proxy_temp \ && mkdir -p /var/cache/nginx/fastcgi_temp \ && mkdir -p /var/cache/nginx/uwsgi_temp \ && mkdir -p /var/cache/nginx/scgi_temp \ && mkdir -p /home/wwwlogs/ \ && mkdir -p /home/wwwroot/myf5/ \ && mkdir -p /etc/letsencrypt/sct/ \ && install -m644 html/index.html /home/wwwroot/myf5/ \ && install -m644 html/50x.html /home/wwwroot/myf5/ \ && install -m755 objs/nginx-debug /usr/sbin/nginx-debug \ && install -m755 objs/ngx_http_xslt_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_xslt_filter_module-debug.so \ && install -m755 objs/ngx_http_image_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_image_filter_module-debug.so \ && install -m755 objs/ngx_http_geoip_module-debug.so /usr/lib/nginx/modules/ngx_http_geoip_module-debug.so \ && install -m755 objs/ngx_http_perl_module-debug.so /usr/lib/nginx/modules/ngx_http_perl_module-debug.so \ && ln -s ../../usr/lib/nginx/modules /usr/local/nginx/modules \ && strip /usr/sbin/nginx* \ && strip /usr/lib/nginx/modules/*.so \ && runDeps="$( \ scanelf --needed --nobanner /usr/sbin/nginx /usr/lib/nginx/modules/*.so \ | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ | sort -u \ | xargs -r apk info --installed \ | sort -u \ )" \ && apk add --virtual .nginx-rundeps $runDeps \ && apk del .build-deps \ && rm -rf /usr/src/openssl-$OPENSSL_VERSION \ && rm -rf /usr/src/nginx-ct-$GX_VERSION_TRANSPARENCY \ && rm -rf /usr/src/ngx_brotli-$NGX_VERSION_BROTLI \ && rm -rf /usr/src/nginx-dynamic_tls.patch \ && rm -rf /usr/src/nginx-$NGINX_VERSION \ && apk add --no-cache gettext \ \ # forward request and error logs to docker log collector && ln -sf /dev/stdout /var/log/nginx/access.log \ && ln -sf /dev/stderr /var/log/nginx/error.log \ # COPY pre defined file, make sure there is correct nginx configuration files, these files are based my own nginx. COPY nginx.conf /usr/local/nginx/conf/nginx.conf COPY enable-php.conf /usr/local/nginx/conf/enable-php.conf COPY fastcgi.conf /usr/local/nginx/conf/fastcgi.conf COPY wordpress.conf /usr/local/nginx/conf/wordpress.conf COPY ./vhost/nginx.vh.default.conf /usr/local/nginx/conf/vhost/default.conf COPY ./ssl/* /usr/local/nginx/conf/ssl/ COPY digicert.sct /etc/letsencrypt/sct/digicert.sct COPY icarus.sct /etc/letsencrypt/sct/icarus.sct COPY Shanghai /etc/localtime ########Start PHP########## RUN echo @testing >> /etc/apk/repositories && \ echo /etc/apk/respositories && \ apk update && \ apk add --no-cache bash \ openssh-client \ wget \ supervisor \ curl \ libcurl \ git \ python \ python-dev \ py-pip \ augeas-dev \ openssl-dev \ ca-certificates \ dialog \ gcc \ musl-dev \ linux-headers \ libmcrypt-dev \ libpng-dev \ icu-dev \ libpq \ libxslt-dev \ libffi-dev \ libmemcached-dev \ cyrus-sasl-dev \ freetype-dev \ sqlite-dev \ libjpeg-turbo-dev && \ docker-php-ext-configure gd \ --with-gd \ --with-freetype-dir=/usr/include/ \ --with-png-dir=/usr/include/ \ --with-jpeg-dir=/usr/include/ && \ #curl iconv session curl -L -o /tmp/memcached.tar.gz http://pecl.php.net/get/memcached-2.2.0.tgz &&\ tar -xzvf /tmp/memcached.tar.gz &&\ mv memcached-2.2.0 /usr/src/php/ext/memcached &&\ rm /tmp/memcached.tar.gz &&\ docker-php-ext-install pdo_mysql pdo_sqlite mysqli mcrypt gd exif intl xsl json soap dom zip opcache memcached && \ docker-php-source delete && \ mkdir -p /var/log/supervisor && \ echo "Asia/Shanghai" > /etc/timezone &&\ EXPECTED_COMPOSER_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig) && \ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \ php -r "if (hash_file('SHA384', 'composer-setup.php') === '${EXPECTED_COMPOSER_SIGNATURE}') { echo 'Composer.phar Installer verified'; } else { echo 'Composer.phar Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \ php composer-setup.php --install-dir=/usr/bin --filename=composer && \ php -r "unlink('composer-setup.php');" && \ #pip install -U pip && \ #pip install -U certbot && \ #mkdir -p /etc/letsencrypt/webrootauth && \ apk del gcc musl-dev linux-headers libffi-dev augeas-dev python-dev &&\ rm -rf /usr/src #############3 tweak php-fpm config################## RUN echo "cgi.fix_pathinfo=0" > ${php_vars} &&\ echo "upload_max_filesize = 100M" >> ${php_vars} &&\ echo "post_max_size = 100M" >> ${php_vars} &&\ echo "variables_order = \"EGPCS\"" >> ${php_vars} && \ echo "memory_limit = 128M" >> ${php_vars} && \ sed -i \ -e "s/;catch_workers_output\s*=\s*yes/catch_workers_output = yes/g" \ -e "s/pm.max_children = 5/pm.max_children = 20/g" \ -e "s/pm.start_servers = 2/pm.start_servers = 10/g" \ -e "s/pm.min_spare_servers = 1/pm.min_spare_servers = 5/g" \ -e "s/pm.max_spare_servers = 3/pm.max_spare_servers = 20/g" \ -e "s/;pm.max_requests = 500/pm.max_requests = 200/g" \ -e "s/user = www-data/user = www/g" \ -e "s/group = www-data/group = www/g" \ -e "s/;listen.mode = 0660/listen.mode = 0666/g" \ -e "s/;listen.owner = www-data/listen.owner = www/g" \ -e "s/;listen.group = www-data/listen.group = www/g" \ -e "s/listen = 127.0.0.1:9000/listen = \/tmp\/php-cgi.sock/g" \ -e "s/^;clear_env = no$/clear_env = no/" \ ${fpm_conf} ADD start.sh /start.sh ADD supervisord.conf /etc/supervisord.conf RUN chmod 755 /start.sh EXPOSE 80/tcp 443/tcp CMD ["/start.sh"] |
镜像制作的一点心得:尽可能的简化需求,力争实现容器本身是一个完整的个体,如果为了所谓的灵活导致过多的考虑外部数据的关联,会使得容器与外部环境耦合性太强,失去了容器本身的意义。但同时又要综合衡量容器化后,内外文件交互的必要性,找到一个灵活的平衡点。起初我是抱着一个容器打天下的思路去看别人的dockerfile,寄希望随便拿一个可以满足自己环境要求的dockerfile来编译,但看了大量dockerfile后发现每个人共享出来的dockerfile其实都是最适合他自己环境的,多多少都会有一些为自己环境定制的东西,所以docker镜像除非标准的官方通用镜像,否则真的是千人千面。正如我build的这个,如果你愿意,可以直接从github上取下并build,build完毕后可以启动使用,但是相关配置、乃至为了适应配置所用的临时证书等都不是通用的。 build后的镜像启动后,别看到启动成功了就认为成功了,一定要 docker logs去仔细看下
[title]启动镜像[/title]
镜像在docker hub以及阿里云上通过拉取github来直接build,由于主机在阿里云上,所以拉取的时候直接从阿里云的docker镜像站拉取就行了。每个人都可以使用阿里云的docker镜像加速,如果自己本地测试也不防将register改为阿里云的,具体方法https://cr.console.aliyun.com登录后查看,每个人都可以得到一个专有的加速连接。
拉取到本地后,重新docker tag一下,不然阿里的仓库名称太长了。随后执行
docker run -itd -v /***/nginx/:/***/nginx/ -v /etc****:/etc/*** -v /***/wwwroot/:/***/wwwroot/:rw --name nginx -p 80:80 -p 443:443 myf5nginx
上述命令里隐去了一些路径。
容器成功启动:
1 2 |
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d35958152c86 mynginx "docker-php-entryp..." About an hour ago Up About an hour 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 9000/tcp nginx |
启动后,首先会发现提示无法连接数据库,因为相关数据库连接配置是使用的localhost且相关权限是只容许本地访问,所以需要对mysql连接即相关数据库用户权限做下改造。
随后可以访问,但是访问网站很慢。这是由于博客启用了memcached,且是通过127.0.0.1来连接的,容器本身内部是没有memecached服务的,所以导致慢,暂时先停掉memcache 功能。
此时博客可以正常访问,而且感觉速度和以前一样。进入博客后台,提示有插件更新,于是更新插件,但是系统提示无权限,看来文件权限属性有了问题,进入容器内看下文件是啥权限:
uid,gid都是1001. 这个1001其实是宿主机本地的某用户的ID,在mount给容器后,容器内运行nginx/php的那个用户并不是这个id而是100,所以php提示无权更新文件。此时需要将容器的那个用户的uid改为1001即可,方法可以是:
1. 在容器内使用usermod去修改那个用户的uid,但是由于是alpine的基础镜像,usermod命令是不存在的,所以不可行
2. build容器的时候,就提前修改好和宿主机一致,这同样需要在容器里增加相关命令安装,但是修改出来的镜像很可能在放到别的宿主机上后,因为别的宿主机上同名用户的uid又不一样了而导致问题
3. 我是比较暴力的将宿主机上的该用户uid改成容器里的100,然后给目录重新chown,这样mount上去的文件就是100了,从而容器用户可以有权限
4. 启动容器时候使用特权参数,这个方法我没测试
由于这方面实践经验较少,不知道在一个复杂的大型生产环境里是如何有效解决这个问题的。从上面可以看出,其实要想实现一个镜像跑遍所有服务器,还不是那么容易的。
解决上述问题后,博客运行完全正常了,ssllabs跑分依旧是A+
[title]mysql容器化[/title]
由于我的mysql没使用innodb,因此我直接将原来本地mysql所用的data目录直接mount给容器使用,这样设置后容器里的mysql确实可以读取到数据库,但是在宿主机查看这个目录时候发现文件用户被设置成了systemd-bus-proxy:input的名称,显然容器用其用户id覆盖了文件权限,这样会影响本地myql未来启动使用这些数据库文件,所以为了保险起见,决定mount一个独立空文件夹给容器,然后做一次标准的mysql数据库文件导入导出工作。这样mysql容器里变拥有了最新的网站数据。然后nginx容器使用link方式连接容器,并设置WP相关数据库server配置为link的别名。
这里无需将mysql暴露给宿主机,因为宿主机用不到,需要注意如果使用-p参数暴露了端口给宿主机,docker自己会自动增加一个iptables规则,这个规则可能会导致内部容器无法连接宿主机IP:port。
docker run --name mysql -v /***/dockerdata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysqlpassword -d mysql:tag[title]memcached容器化[/title]
使用官方的memcached镜像启动,nginx-php容器启动时候link过去。根据WP的memcached插件提示在WP配置文件里增加自定义memcached server的配置,使用link时候设置的别名即可,例如memcached:11211
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
WordPress Memcached Status Using the PHP Memcached class to interact with Memcached memcached: memcached:11211 pid: 1 uptime: 901 threads: 4 time: 1501245716 pointer_size: 64 rusage_user_seconds: 0 rusage_user_microseconds: 185891 rusage_system_seconds: 0 rusage_system_microseconds: 201686 curr_items: 912 total_items: 995 limit_maxbytes: 67108864 curr_connections: 10 total_connections: 81 connection_structures: 12 bytes: 816153 cmd_get: 2473 cmd_set: 1172 get_hits: 1718 get_misses: 755 evictions: 0 bytes_read: 1360313 bytes_written: 4427476 version: 1.5.0 |
docker run -itd --name mymemcached memcached -m 64
最终Nginx容器的run命令是:
docker run -itd -v /***/nginx/:/***/nginx/ -v /***/letsencrypt:/***/letsencrypt/ -v /***/www/:/***/www/:rw --name nginx --link=mymemcached:memcached --link=mysql:mysql -p 80:80 -p 443:443 mynginx[title]Docker Compose管理所有容器[/title]
除了上述手工运行命令逐个容器启动外,还可以利用docker-compose来实现统一启动和管理,有点类似kubernetes的deployment。保存以下文件为docker-compose.yml, 然后在同目录下执行 docker-compose up -d 启动所有服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
version: "3" services: mysql: image: mysql:5.7.18 container_name: mysql volumes: - /***/dockerdata:/***/mysqldata environment: - MYSQL_ROOT_PASSWORD=**** memcached: image: memcached container_name: mymemcached command: ["-m", "64"] nginx: image: mynginx container_name: nginx volumes: - /****/nginx/:/***/nginx/ - /etc/letsencrypt:/etc/letsencrypt/ - /***/root/:/***/oot/:rw depends_on: - mysql - memcached links: - mysql:mysql - memcached:memcached ports: - 80:80 - 443:443 |
其它处理,修改letsencrypt相关脚本适应容器化后的nginx配置加载
<EOF>
实践过程中参考了:
https://hub.docker.com/r/panascais/nginx/
https://hub.docker.com/r/frostiede/nginx/~/dockerfile/
https://github.com/ngineered/nginx-php-fpm/blob/master/Dockerfile
https://xlange.com/post/dockerfile-baseon-official-php-image.html
如果对此感兴趣,此文相关dockerfile及文件放置在github上:https://github.com/myf5/nginx-php-fpm
hub上镜像文件: https://hub.docker.com/r/myf5/
微信公众号
文章评论