基于容器手动制作镜像步骤具体如下:
下载一个系统的官方基础镜像,如: CentOS 或 Ubuntu
基于基础镜像启动一个容器,并进入到容器
在容器里面做配置操作
安装基础命令
配置运行环境
安装服务和配置服务
放业务程序代码
提交为一个新镜像 docker commit
基于自己的的镜像创建容器并测试访问
注意:手动制作镜像的方式显示全部的容器制作过程比较困难,且需要前台执行方式添加命令不支持自动化,生产不实用
作为帮助理解镜像的制作过程就比较容易理解
案例:利用官方提供的基础镜像制作一个初始化镜像并提交到docker官方,再从官方拉取镜像到本地
#下载一个系统的官方基础镜像[root@ubuntu2204 ~]#docker pull alpine[root@ubuntu2204 ~]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine latest 49176f190c7e 6 weeks ago 7.05MB#基于基础镜像启动一个容器,并进入到容器[root@ubuntu2204 ~]#docker run -it –name alpine-image-latest alpine:latest sh#在容器里面做配置操作 – 把容器的镜像源替换成国内地址[root@ubuntu2204 ~]#docker cp alpine-image-latest:/etc/apk/repositories .[root@ubuntu2204 ~]#cat repositories https://dl-cdn.alpinelinux.org/alpine/v3.17/mainhttps://dl-cdn.alpinelinux.org/alpine/v3.17/community[root@ubuntu2204 ~]#sed -i ‘s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/’ repositories [root@ubuntu2204 ~]#cat repositories https://mirrors.ustc.edu.cn/alpine/v3.17/mainhttps://mirrors.ustc.edu.cn/alpine/v3.17/community[root@ubuntu2204 ~]#docker cp repositories alpine-image-latest:/etc/apk/repositories [root@ubuntu2204 ~]#docker exec -it alpine-image-latest sh/ # cat /etc/apk/repositories https://mirrors.ustc.edu.cn/alpine/v3.17/mainhttps://mirrors.ustc.edu.cn/alpine/v3.17/community#安装常用软件/ # apk update && apk –no-cache add iotop gcc libgcc libc-dev libcurl libc-utilspcre-dev zlib-dev libnfs make pcre pcre2 zip unzip net-tools pstree wget tzdatalibevent libevent-dev iproute2 vim/ # apk exit#基于现有容器提交为一个新镜像 docker commit[root@ubuntu2204 ~]#docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES027d1de130f0 alpine:latest “sh” 8 minutes ago Exited (0) About a minute ago alpine-image-latest[root@ubuntu2204 ~]#docker commit”docker commit” requires at least 1 and at most 2 arguments.See ‘docker commit –help’.Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]Create a new image from a container’s changes[root@ubuntu2204 ~]#docker commit alpine-image-latest alpine-mooreyxia:latestsha256:c837f55017976cf25c4a8b257967019eccba0d8ac67e284242434f1ac668a44c[root@ubuntu2204 ~]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine-mooreyxia latest c837f5501797 6 seconds ago 221MBalpine latest 49176f190c7e 6 weeks ago 7.05MB#上传到hub镜像官网[root@ubuntu2204 ~]#docker tag alpine-mooreyxia:latest mooreyxia/alpine-mooreyxia-20230107:latest[root@ubuntu2204 ~]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine-mooreyxia latest c837f5501797 36 minutes ago 221MBmooreyxia/alpine-mooreyxia-20230107 latest c837f5501797 36 minutes ago 221MBbusybox latest 827365c7baf1 2 weeks ago 4.86MBnginx latest 1403e55ab369 2 weeks ago 142MBubuntu latest 6b7dfa7e8fdb 4 weeks ago 77.8MBalpine latest 49176f190c7e 6 weeks ago 7.05MBhello-world latest feb5d9fea6a5 15 months ago 13.3kBmysql 5.7.32 cc8775c0fe94 24 months ago 449MB[root@ubuntu2204 ~]#docker loginLogin with your Docker ID to push and pull images from Docker Hub. If you don’t have a Docker ID, head over to https://hub.docker.com to create one.Username: mooreyxiaPassword: WARNING! Your password will be stored unencrypted in /root/.docker/config.json.Configure a credential helper to remove this warning. Seehttps://docs.docker.com/engine/reference/commandline/login/#credentials-storeLogin Succeeded[root@ubuntu2204 ~]#docker push mooreyxia/alpine-mooreyxia-20230107:latestThe push refers to repository [docker.io/mooreyxia/alpine-mooreyxia-20230107]8d5f0fc33c4a: Pushed ded7a220bb05: Mounted from library/alpine latest: digest: sha256:4e87307a2a9e831d181c5a42b2660e5dce1d0d2e3d0916f6b649b1cef1cdc477 size: 740
#在另外一台机子上拉取镜像,默认是公开镜像,可以直接使用[root@ubuntu2204 ~]#docker pull mooreyxia/alpine-mooreyxia-20230107:latestlatest: Pulling from mooreyxia/alpine-mooreyxia-20230107c158987b0551: Pull complete e2cd06421e04: Pull complete Digest: sha256:4e87307a2a9e831d181c5a42b2660e5dce1d0d2e3d0916f6b649b1cef1cdc477Status: Downloaded newer image for mooreyxia/alpine-mooreyxia-20230107:latestdocker.io/mooreyxia/alpine-mooreyxia-20230107:latest[root@ubuntu2204 ~]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEmooreyxia/alpine-mooreyxia-20230107 latest c837f5501797 Less than a second ago 221MB#测试[root@ubuntu2204 ~]#docker run -d –name alpine-test1 mooreyxia/alpine-mooreyxia-20230107 sleep 1000b5328aa1a2e76e09ed1628a2e9763a716837a0285ae01a2cff322cbddc57da99[root@ubuntu2204 ~]#docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESb5328aa1a2e7 mooreyxia/alpine-mooreyxia-20230107 “sleep 1000” 4 seconds ago Up 2 seconds alpine-test1[root@ubuntu2204 ~]#docker exec -it alpine-test1 sh/ # ss -ntlpState Recv-Q Send-Q Local Address:Port Peer Address:Port Process / # exit
利用 DockerFile 文件执行 docker build 自动构建镜像
Dockerfile 介绍
Docker程序读取DockerFile并根据指令生成Docker镜像,相比手动制作镜像的方式,DockerFile更能直观的展示镜像是怎么产生的,有了DockerFile,当后期有额外的需求时,只要在之前的DockerFile添加或者修改响应的命令即可重新生成新的Docker镜像,避免了重复手动制作镜像的麻烦,类似与shell脚本一样,可以方便高效的制作镜像
Docker将尽可能重用中间镜像层(缓存),以显著加速docker build 命令的执行过程,这由Usingcache 控制台输出中的消息指示
案例:
#查看nginx官方的镜像指令执行结果[root@ubuntu2204 ~]#docker pull nginx:1.22.01.22.0: Pulling from library/nginxbd159e379b3b: Pull complete 265da2307f4a: Pull complete 9f5a323076dc: Pull complete 1cb127bd9321: Pull complete 20d83d630f2b: Pull complete e0c68760750a: Pull complete Digest: sha256:f0d28f2047853cbc10732d6eaa1b57f1f4db9b017679b9fd7966b6a2f9ccc2d1Status: Downloaded newer image for nginx:1.22.0docker.io/library/nginx:1.22.0#可以看到镜像指令从下往上一层层执行[root@ubuntu2204 ~]#docker history nginx:1.22.0IMAGE CREATED CREATED BY SIZE COMMENT08a1cbf9c69e 3 months ago /bin/sh -c #(nop) CMD [“nginx” “-g” “daemon… 0B 3 months ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B 3 months ago /bin/sh -c #(nop) EXPOSE 80 0B 3 months ago /bin/sh -c #(nop) ENTRYPOINT [“/docker-entr… 0B 3 months ago /bin/sh -c #(nop) COPY file:09a214a3e07c919a… 4.61kB 3 months ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB 3 months ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0… 1.96kB 3 months ago /bin/sh -c #(nop) COPY file:65504f71f5855ca0… 1.2kB 3 months ago /bin/sh -c set -x && addgroup –system -… 61.1MB 3 months ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~bullseye 0B 3 months ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.6 0B 3 months ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.22.0 0B 3 months ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B 3 months ago /bin/sh -c #(nop) CMD [“bash”] 0B 3 months ago /bin/sh -c #(nop) ADD file:b78b777208be08edd… 80.5MB
官方的 Docker file 脚本
https://github.com/nginxinc/docker-nginx/blob/fef51235521d1cdf8b05d8cb1378a526d2abf421/stable/debian/Dockerfile## NOTE: THIS DOCKERFILE IS GENERATED VIA “update.sh”## PLEASE DO NOT EDIT IT DIRECTLY.#FROM debian:bullseye-slimLABEL maintainer=”NGINX Docker Maintainers “ENV NGINX_VERSION 1.22.1ENV NJS_VERSION 0.7.7ENV PKG_RELEASE 1~bullseyeRUN set -x # create nginx user/group first, to be consistent throughout docker variants && addgroup –system –gid 101 nginx && adduser –system –disabled-login –ingroup nginx –no-create-home –home /nonexistent –gecos “nginx user” –shell /bin/false –uid 101 nginx && apt-get update && apt-get install –no-install-recommends –no-install-suggests -y gnupg1 ca-certificates && NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; found=”; for server in hkp://keyserver.ubuntu.com:80 pgp.mit.edu ; do echo “Fetching GPG key $NGINX_GPGKEY from $server”; apt-key adv –keyserver “$server” –keyserver-options timeout=10 –recv-keys “$NGINX_GPGKEY” && found=yes && break; done; test -z “$found” && echo >&2 “error: failed to fetch GPG key $NGINX_GPGKEY” && exit 1; apt-get remove –purge –auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* && dpkgArch=”$(dpkg –print-architecture)” && nginxPackages=” nginx=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} ” && case “$dpkgArch” in amd64|arm64) # arches officialy built by upstream echo “deb https://nginx.org/packages/debian/ bullseye nginx” >> /etc/apt/sources.list.d/nginx.list && apt-get update ;; *) # we’re on an architecture upstream doesn’t officially build for# let’s build binaries from the published source packages echo “deb-src https://nginx.org/packages/debian/ bullseye nginx” >> /etc/apt/sources.list.d/nginx.list # new directory for storing sources and .deb files && tempDir=”$(mktemp -d)” && chmod 777 “$tempDir” # (777 to ensure APT’s “_apt” user can access it too) # save list of currently-installed packages so build dependencies can be cleanly removed later && savedAptMark=”$(apt-mark showmanual)” # build .deb files from upstream’s source packages (which are verified by apt-get) && apt-get update && apt-get build-dep -y $nginxPackages && ( cd “$tempDir” && DEB_BUILD_OPTIONS=”nocheck parallel=$(nproc)” apt-get source –compile $nginxPackages ) # we don’t remove APT lists here because they get re-downloaded and removed later # reset apt-mark’s “manual” list so that “purge –auto-remove” will remove all build dependencies# (which is done after we install the built packages so we don’t have to redownload any overlapping dependencies) && apt-mark showmanual | xargs apt-mark auto > /dev/null && { [ -z “$savedAptMark” ] || apt-mark manual $savedAptMark; } # create a temporary local APT repo to install from (so that dependency resolution can be handled by APT, as it should be) && ls -lAFh “$tempDir” && ( cd “$tempDir” && dpkg-scanpackages . > Packages ) && grep ‘^Package: ‘ “$tempDir/Packages” && echo “deb [ trusted=yes ] file://$tempDir ./” > /etc/apt/sources.list.d/temp.list # work around the following APT issue by using “Acquire::GzipIndexes=false” (overriding “/etc/apt/apt.conf.d/docker-gzip-indexes”)# Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages – open (13: Permission denied)# …# E: Failed to fetch store:/var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages – open (13: Permission denied) && apt-get -o Acquire::GzipIndexes=false update ;; esac && apt-get install –no-install-recommends –no-install-suggests -y $nginxPackages gettext-base curl && apt-get remove –purge –auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list # if we have leftovers from building, let’s purge them (including extra, unnecessary build deps) && if [ -n “$tempDir” ]; then apt-get purge -y –auto-remove && rm -rf “$tempDir” /etc/apt/sources.list.d/temp.list; fi # 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 # create a docker-entrypoint.d directory && mkdir /docker-entrypoint.dCOPY docker-entrypoint.sh /COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.dCOPY 20-envsubst-on-templates.sh /docker-entrypoint.dCOPY 30-tune-worker-processes.sh /docker-entrypoint.dENTRYPOINT [“/docker-entrypoint.sh”]EXPOSE 80STOPSIGNAL SIGQUITCMD [“nginx”, “-g”, “daemon off;”]
Dockerfile文件的制作镜像的分层结构
案例:
#按照业务类型或系统类型等方式划分创建目录环境,方便后期镜像比较多的时候进行分类[root@ubuntu2204 ~]#mkdir /data/dockerfile/{web/{nginx,apache,tomcat,jdk},system/{centos,ubuntu,alpine,debian}} -p[root@ubuntu2204 ~]#tree /data/dockerfile//data/dockerfile/├── system│ ├── alpine│ ├── centos│ ├── debian│ └── ubuntu└── web ├── apache ├── jdk ├── nginx └── tomcat10 directories, 0 files
Dockerfile 文件格式
每一行以Dockerfile的指令开头,指令不区分大小写,但是惯例使用大写
使用 # 开始作为注释
每一行只支持一条指令,每条指令可以携带多个参数
指令按文件的顺序从上至下进行执行
每个指令的执行会生成一个新的镜像层,为了减少分层和镜像大小,尽可能将多条指令合并成一条指令
制作镜像一般可能需要反复多次,每次执行dockfile都按顺序执行,从头开始,已经执行过的指令自动缓存,不需要再执行,如果后续有一行新的指令没执行过,其往后的指令将会重新执行,所以为加速镜像制作,将最常变化的内容放下dockerfile的文件的后面
#dockerfile 文件中的常见指令:ADDCOPYENVEXPOSEFROMLABELSTOPSIGNALUSERVOLUMEWORKDIR
FROM: 指定基础镜像
FROM 指定基础镜像,此指令通常必需放在Dockerfile文件第一个非注释行。后续的指令都是运行于此基准镜像所提供的运行环境
#格式:FROM [–platform=] [:] [AS ]*说明: 关于scratch 镜像该镜像是一个空的镜像,可以用于构建busybox等超小镜像
案例:Dockerfile 制作基于基础镜像的Base镜像 – 将上述手工制作镜像方式用Docker file的方式实现
#准备目录结构,下载镜像并初始化系统[root@ubuntu2204 ~]#mkdir /data/dockerfile/{web/{nginx,apache,tomcat,jdk},system/{centos,ubuntu,alpine,debian}} -p[root@ubuntu2204 ~]#tree /data/dockerfile//data/dockerfile/├── system│ ├── alpine│ ├── centos│ ├── debian│ └── ubuntu└── web ├── apache ├── jdk ├── nginx └── tomcat10 directories, 0 files#下载基础镜像[root@ubuntu2204 ~]#docker pull alpine[root@ubuntu2204 ~]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEbusybox latest 827365c7baf1 2 weeks ago 4.86MBnginx latest 1403e55ab369 2 weeks ago 142MBubuntu latest 6b7dfa7e8fdb 4 weeks ago 77.8MBalpine latest 49176f190c7e 6 weeks ago 7.05MBnginx 1.22.0 08a1cbf9c69e 3 months ago 142MBmysql 5.7.32 cc8775c0fe94 24 months ago 449MB#先制作基于基础镜像的系统Base镜像[root@ubuntu2204 ~]#cd /data/dockerfile/system/alpine/#创建Dockerfile,注意可以是dockerfile,但无语法着色功能[root@ubuntu2204 alpine]#vim Dockerfile[root@ubuntu2204 alpine]#cat Dockerfile#This is moore test dockerfileFROM alpine:latest#可以指定镜像元数据,如: 镜像作者等,可以有多个LABEL maintainer=”mooreyxia.org”#或者#ABEL Auther=”mooreyxia”# Version=”v1″RUN sed -i ‘s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/’ /etc/apk/repositories && apk update && apk –no-cache add iotop gcc libgcc libc-dev libcurl libc-utils pcre-dev zlib-dev libnfs make pcre pcre2 zip unzip net-tools pstree wget libevent libevent-dev iproute2 tzdata vim [root@ubuntu2204 alpine]#vim build.sh[root@ubuntu2204 alpine]#cat build.sh#!/bin/bash##********************************************************************#Author: mooreyxia#Date: 2023-01-09#FileName: build.sh#Description: The test script#Copyright (C): 2023 All rights reserved#********************************************************************#!/bin/bash#docker build -t alpine-base:v1 .#执行[root@ubuntu2204 alpine]#./build.sh Sending build context to Docker daemon 3.072kBStep 1/3 : FROM alpine:latest —> 49176f190c7eStep 2/3 : LABEL maintainer=”mooreyxia.org” —> Running in c806bbbfc0bbRemoving intermediate container c806bbbfc0bb —> bb5f403269acStep 3/3 : RUN sed -i ‘s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/’ /etc/apk/repositories && apk update && apk –no-cache add iotop gcc libgcc libc-dev libcurl libc-utils pcre-dev zlib-dev libnfs make pcre pcre2 zip unzip net-tools pstree wget libevent libevent-dev iproute2 —> Running in 853b191ec719fetch https://mirrors.ustc.edu.cn/alpine/v3.17/main/x86_64/APKINDEX.tar.gzfetch https://mirrors.ustc.edu.cn/alpine/v3.17/community/x86_64/APKINDEX.tar.gzv3.17.0-332-g13963e2526 [https://mirrors.ustc.edu.cn/alpine/v3.17/main]v3.17.0-348-g6f4479fa00 [https://mirrors.ustc.edu.cn/alpine/v3.17/community]OK: 17816 distinct packages availablefetch https://mirrors.ustc.edu.cn/alpine/v3.17/main/x86_64/APKINDEX.tar.gzfetch https://mirrors.ustc.edu.cn/alpine/v3.17/community/x86_64/APKINDEX.tar.gz(1/58) Installing libgcc (12.2.1_git20220924-r4)(2/58) Installing libstdc++ (12.2.1_git20220924-r4)(3/58) Installing binutils (2.39-r2)(4/58) Installing libgomp (12.2.1_git20220924-r4)(5/58) Installing libatomic (12.2.1_git20220924-r4)(6/58) Installing gmp (6.2.1-r2)(7/58) Installing isl25 (0.25-r0)(8/58) Installing mpfr4 (4.1.0-r0)(9/58) Installing mpc1 (1.2.1-r1)(10/58) Installing gcc (12.2.1_git20220924-r4)(11/58) Installing libbz2 (1.0.8-r4)(12/58) Installing libexpat (2.5.0-r0)(13/58) Installing libffi (3.4.4-r0)(14/58) Installing gdbm (1.23-r0)(15/58) Installing xz-libs (5.2.9-r0)(16/58) Installing mpdecimal (2.5.1-r1)(17/58) Installing ncurses-terminfo-base (6.3_p20221119-r0)(18/58) Installing ncurses-libs (6.3_p20221119-r0)(19/58) Installing readline (8.2.0-r0)(20/58) Installing sqlite-libs (3.40.1-r0)(21/58) Installing python3 (3.10.9-r1)(22/58) Installing iotop (0.6-r9)(23/58) Installing libcap2 (2.66-r0)(24/58) Installing musl-fts (1.2.7-r3)(25/58) Installing libelf (0.187-r2)(26/58) Installing libmnl (1.0.5-r0)(27/58) Installing iproute2-minimal (6.0.0-r1)(28/58) Installing libnftnl (1.2.4-r0)(29/58) Installing iptables (1.8.8-r2)(30/58) Installing iproute2-tc (6.0.0-r1)(31/58) Installing iproute2-ss (6.0.0-r1)(32/58) Installing iproute2 (6.0.0-r1)Executing iproute2-6.0.0-r1.post-install(33/58) Installing musl-dev (1.2.3-r4)(34/58) Installing libc-dev (0.7.2-r3)(35/58) Installing ca-certificates (20220614-r3)(36/58) Installing brotli-libs (1.0.9-r9)(37/58) Installing nghttp2-libs (1.51.0-r0)(38/58) Installing libcurl (7.87.0-r0)(39/58) Installing libevent (2.1.12-r5)(40/58) Installing pkgconf (1.9.3-r0)(41/58) Installing libevent-dev (2.1.12-r5)(42/58) Installing libnfs (5.0.2-r0)(43/58) Installing make (4.3-r1)(44/58) Installing mii-tool (2.10-r0)(45/58) Installing net-tools (2.10-r0)(46/58) Installing pcre (8.45-r2)(47/58) Installing libpcre16 (8.45-r2)(48/58) Installing libpcre32 (8.45-r2)(49/58) Installing libpcrecpp (8.45-r2)(50/58) Installing pcre-dev (8.45-r2)(51/58) Installing pcre2 (10.42-r0)(52/58) Installing pstree (2.40-r0)(53/58) Installing unzip (6.0-r13)(54/58) Installing libunistring (1.1-r0)(55/58) Installing libidn2 (2.3.4-r0)(56/58) Installing wget (1.21.3-r2)(57/58) Installing zip (3.0-r10)(58/58) Installing zlib-dev (1.2.13-r0)Executing busybox-1.35.0-r29.triggerExecuting ca-certificates-20220614-r3.triggerOK: 219 MiB in 73 packagesRemoving intermediate container 853b191ec719 —> 4c7eada5d9cfSuccessfully built 4c7eada5d9cfSuccessfully tagged alpine-base:v1[root@ubuntu2204 alpine]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine-base v1 4c7eada5d9cf 2 minutes ago 221MBalpine latest 49176f190c7e 6 weeks ago 7.05MB[root@ubuntu2204 alpine]#docker inspect 4c7eada5d9cf[ { “Id”: “sha256:4c7eada5d9cfcc8e0ad050751cd18c5441b82b80e1309acf51bf34ba71954040”, “RepoTags”: [ “alpine-base:v1” ], “RepoDigests”: [], “Parent”: “sha256:bb5f403269acf4ab918a154dfbedfdf479a2ae81fd3a4aa74ba88674289ba7ab”, “Comment”: “”, “Created”: “2023-01-09T08:25:42.38248152Z”, “Container”: “853b191ec7194f38a30381f26d1a47328541ce42723f384c0b0a262bc6049b46”, “ContainerConfig”: { “Hostname”: “”, “Domainname”: “”, “User”: “”, “AttachStdin”: false, “AttachStdout”: false, “AttachStderr”: false, “Tty”: false, “OpenStdin”: false, “StdinOnce”: false, “Env”: [ “PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin” ], “Cmd”: [ “/bin/sh”, “-c”, “sed -i ‘s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/’ /etc/apk/repositories && apk update && apk –no-cache add iotop gcc libgcc libc-dev libcurl libc-utils pcre-dev zlib-dev libnfs make pcre pcre2 zip unzip net-tools pstree wget libevent libevent-dev iproute2” ], “Image”: “sha256:bb5f403269acf4ab918a154dfbedfdf479a2ae81fd3a4aa74ba88674289ba7ab”, “Volumes”: null, “WorkingDir”: “”, “Entrypoint”: null, “OnBuild”: null, “Labels”: { “maintainer”: “mooreyxia.org” } }, “DockerVersion”: “20.10.12”, “Author”: “”, “Config”: { “Hostname”: “”, “Domainname”: “”, “User”: “”, “AttachStdin”: false, “AttachStdout”: false, “AttachStderr”: false, “Tty”: false, “OpenStdin”: false, “StdinOnce”: false, “Env”: [ “PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin” ], “Cmd”: [ “/bin/sh” ], “Image”: “sha256:bb5f403269acf4ab91
8a154dfbedfdf479a2ae81fd3a4aa74ba88674289ba7ab”, “Volumes”: null, “WorkingDir”: “”, “Entrypoint”: null, “OnBuild”: null, “Labels”: { “maintainer”: “mooreyxia.org” } }, “Architecture”: “amd64”, “Os”: “linux”, “Size”: 221144303, “VirtualSize”: 221144303, “GraphDriver”: { “Data”: { “LowerDir”: “/data/docker/overlay2/4f1e571f109868c90e38a6f0538c5abf9daff5c47ffedb19894d56ce1edd9b37/diff”, “MergedDir”: “/data/docker/overlay2/57aa8f1abaf5561f9fb390f5e7f3888a64124b5b78c667d6ded505500a74178d/merged”, “UpperDir”: “/data/docker/overlay2/57aa8f1abaf5561f9fb390f5e7f3888a64124b5b78c667d6ded505500a74178d/diff”, “WorkDir”: “/data/docker/overlay2/57aa8f1abaf5561f9fb390f5e7f3888a64124b5b78c667d6ded505500a74178d/work” }, “Name”: “overlay2” }, “RootFS”: { “Type”: “layers”, “Layers”: [ “sha256:ded7a220bb058e28ee3254fbba04ca90b679070424424761a53a043b93b612bf”, “sha256:c8619f65f27764df0501bd18b0133d2573c6ba0d773db3064a7e2032ff265603” ] }, “Metadata”: { “LastTagTime”: “2023-01-09T16:25:42.994959939+08:00” } }]
RUN: 执行 shell命令
RUN 指令用来在构建镜像阶段需要执行 FROM 指定镜像所支持的Shell命令
RUN 可以写多个,每一个RUN指令都会建立一个镜像层,所以尽可能合并成一条指令,比如将多个shell命令通过 && 连接一起成为在一条指令
每个RUN都是独立运行的,和前一个RUN无关 – 例如进入目录创建文件 cd && touch 而非分层
案例:
RUN sed -i ‘s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/’ /etc/apk/repositories && apk update && apk –no-cache add iotop gcc libgcc libc-dev libcurl libc-utils pcre-dev zlib-dev libnfs make pcre pcre2 zip unzip net-tools pstree wget libevent libevent-dev iproute2 tzdata vim && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo “Asia/Shanghai” > /etc/timezone
构建镜像docker build 命令
docker build [OPTIONS] PATH | URL | -说明:PATH | URL | – #可以使是本地路径,也可以是URL路径。若设置为 – ,则从标准输入获取Dockerfile的内容-f, –file string #Dockerfile文件名,默认为 PATH/Dockerfile–force-rm #总是删除中间层容器,创建镜像失败时,删除临时容器–no-cache #不使用之前构建中创建的缓存-q –quiet=false #不显示Dockerfile的RUN运行的输出结果–rm=true #创建镜像成功时,删除临时容器-t –tag list #设置注册名称、镜像名称、标签。格式为 /:(标签默认为latest)#案例:[root@ubuntu2204 alpine]#cat build.sh #!/bin/bash##********************************************************************#Author: mooreyxia#Date: 2023-01-09#FileName: build.sh#Description: The test script#Copyright (C): 2023 All rights reserved#********************************************************************#!/bin/bash#TAG=$1docker build -t alpine-base:$1 .[root@ubuntu2204 alpine]#. build.sh v1.1Sending build context to Docker daemon 3.584kBStep 1/3 : FROM alpine:latest —> 49176f190c7eStep 2/3 : LABEL Auther=”mooreyxia” Version=”v1″ —> Using cache —> f21dcac829d1Step 3/3 : RUN sed -i ‘s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/’ /etc/apk/repositories && apk update && apk –no-cache add iotop gcc libgcc libc-dev libcurl libc-utils pcre-dev zlib-dev libnfs make pcre pcre2 zip unzip net-tools pstree wget libevent libevent-dev iproute2 tzdata vim && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo “Asia/Shanghai” > /etc/timezone —> Using cache —> 4f60390e7b99Successfully built 4f60390e7b99Successfully tagged alpine-base:v1.1[root@ubuntu2204 alpine]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine-base v1.1 4f60390e7b99 3 minutes ago 250MBalpine-base v1.2 4f60390e7b99 3 minutes ago 250MB 90c6146f1aac 16 minutes ago 222MB –> 重新构建一个镜像时原来镜像会挂起alpine-base v1 148f122d0a66 37 minutes ago 221MBbusybox latest 827365c7baf1 2 weeks ago 4.86MBnginx latest 1403e55ab369 2 weeks ago 142MBubuntu latest 6b7dfa7e8fdb 4 weeks ago 77.8MBalpine latest 49176f190c7e 6 weeks ago 7.05MBnginx 1.22.0 08a1cbf9c69e 3 months ago 142MBmysql 5.7.32 cc8775c0fe94 24 months ago 449MB
prune删除所有不使用包括挂起的镜像
案例:
[root@ubuntu2204 alpine]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine-base v1.1 4f60390e7b99 3 minutes ago 250MB 90c6146f1aac 16 minutes ago 222MB –> 重新构建一个镜像时原来镜像会挂起[root@ubuntu2204 ~]#docker system prune –helpUsage: docker system prune [OPTIONS]Remove unused dataOptions: -a, –all Remove all unused images not just dangling ones –filter filter Provide filter values (e.g. ‘label==’) -f, –force Do not prompt for confirmation –volumes Prune volumes[root@ubuntu2204 ~]#docker system prune WARNING! This will remove: – all stopped containers – all networks not used by at least one container – all dangling images – all dangling build cacheAre you sure you want to continue? [y/N] y Deleted Containers:57a886a0346ae833425b3efab9deab277de3f5a6ad6033c255fcd0a3a841eb2ca295f70ed1036d2a8d55103d4c2cd43f0d2da3146ac36f7070e0bd98c7fc639aDeleted Images:deleted: sha256:90c6146f1aac717807fefee47b9643170a6f637717ab2d571da553fc6c2fc430deleted: sha256:fe020435cafaa4e384c938696469c3595b23bb30a7e0dc284e7479015913c8e8Total reclaimed space: 489.1MB
ADD: 复制和解包文件
该命令可认为是增强版的COPY,不仅支持COPY,还支持自动解压缩。
可以是Dockerfile所在目录的一个相对路径;也可是一个 URL;还可是一个 tar 文件(自动解压)
可以是绝对路径或者是WORKDIR 指定的相对路径
如果是目录,只复制目录中的内容,而非目录本身
如果是一个 URL ,下载后的文件权限自动设置为 600
如果为URL且不以/结尾,则指定的文件将被下载并直接被创建为,如果以 / 结尾,则文件名URL指定的文件将被直接下载并保存为/
如果是一个本地文件系统上的打包文件,如: gz, bz2 ,xz ,它将被解包 ,其行为类似于”tar -x”命令,但是通过URL获取到的tar文件将不会自动展开
如果有多个,或其间接或直接使用了通配符,则必须是一个以/结尾的目录路径;如果不以/结尾,则其被视作一个普通文件,的内容将被直接写入到
案例:基于 Alpine 基础镜像制作 Nginx 镜像
#制作基于 Alpine 自定义镜像的 Nginx 镜像[root@ubuntu2204 ~]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine-base v1.2 4f60390e7b99 2 hours ago 250MB[root@ubuntu2204 alpine]#tree /data/dockerfile//data/dockerfile/├── system│ ├── alpine│ │ ├── build.sh│ │ └── Dockerfile│ ├── centos│ ├── debian│ └── ubuntu└── web ├── apache ├── jdk ├── nginx └── tomcat[root@ubuntu2204 alpine]#cd /data/dockerfile/web/nginx/[root@ubuntu2204 nginx]#mkdir 1.16.1-alpine -pv[root@ubuntu2204 nginx]#ll总用量 16drwxr-xr-x 4 root root 4096 1月 9 19:41 ./drwxr-xr-x 6 root root 4096 1月 9 15:38 ../drwxr-xr-x 2 root root 4096 1月 9 19:41 1.16.1-alpine/[root@ubuntu2204 nginx]#cd 1.16.1-alpine/[root@ubuntu2204 1.16.1-alpine]#wget http://nginx.org/download/nginx-1.16.1.tar.gz–2023-01-09 19:44:49– http://nginx.org/download/nginx-1.16.1.tar.gz正在解析主机 nginx.org (nginx.org)… 52.58.199.22, 3.125.197.172, 2a05:d014:edb:5702::6, …正在连接 nginx.org (nginx.org)|52.58.199.22|:80… 已连接。已发出 HTTP 请求,正在等待回应… 200 OK长度: 1032630 (1008K) [application/octet-stream]正在保存至: ‘nginx-1.16.1.tar.gz’nginx-1.16.1.tar.gz 100%[=============================================================================================>] 1008K 25.5KB/s 用时 69s 2023-01-09 19:46:00 (14.6 KB/s) – 已保存 ‘nginx-1.16.1.tar.gz’ [1032630/1032630])[root@ubuntu2204 1.16.1-alpine]#ll总用量 1020drwxr-xr-x 2 root root 4096 1月 9 19:44 ./drwxr-xr-x 3 root root 4096 1月 9 19:42 ../-rw-r–r– 1 root root 1032630 8月 14 2019 nginx-1.16.1.tar.gz[root@ubuntu2204 1.16.1-alpine]#echo Test Page based nginx-alpine > index.html[root@ubuntu2204 1.16.1-alpine]#vim nginx.conf[root@ubuntu2204 1.16.1-alpine]#cat nginx.conf user nginx;worker_processes 1;daemon off; *****#error_log logs/error.log;#error_log logs/error.log notice;#error_log logs/error.log info;#pid logs/nginx.pid;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; #log_format main ‘$remote_addr – $remote_user [$time_local] “$request” ‘ # ‘$status $body_bytes_sent “$http_referer” ‘ # ‘”$http_user_agent” “$http_x_forwarded_for”‘; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root /data/nginx/html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ .php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ .php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache’s document root # concurs with nginx’s one # #location ~ /.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #}}[root@ubuntu2204 1.16.1-alpine]#ll总用量 1028drwxr-xr-x 2 root root 4096 1月 9 20:04 ./drwxr-xr-x 3 root root 4096 1月 9 19:42 ../-rw-r–r– 1 root root 29 1月 9 19:47 index.html-rw-r–r– 1 root root 1032630 8月 14 2019 nginx-1.16.1.tar.gz-rw-r–r– 1 root root 558 1月 9 20:04 nginx.conf#编写Dockerfile文件[root@ubuntu2204 1.16.1-alpine]#vim Dockerfile[root@ubuntu2204 1.16.1-alpine]#cat Dockerfile FROM alpine-base:v1.2LABEL author=”mooreyxia” version=”1.0″ ADD nginx-1.16.1.tar.gz /usr/local/srcRUN cd /usr/local/src/nginx-1.16.1 && ./configure –prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/bin/ RUN addgroup -g 2019 -S nginx && adduser -s /sbin/nologin -S -D -u 2019 -G nginx nginxCOPY nginx.conf /apps/nginx/conf/nginx.confADD index.html /data/nginx/html/index.htmlRUN chown -R nginx.nginx /data/nginx/ /apps/nginx/EXPOSE 80 443CMD [“nginx”]#构建镜像[root@ubuntu2204 1.16.1-alpine]#vim build.sh[root@ubuntu2204 1.16.1-alpine]#cat build.sh #!/bin/bash##********************************************************************#Author: mooreyxia#Date: 2023-01-09#FileName: build.sh#Description: The test script#Copyright (C): 2023 All rights reserved#********************************************************************#!/bin/bashdocker build -t nginx-alpine:1.16.1 .[root@ubuntu2204 1.16.1-alpine]#lsbuild.sh Dockerfile index.html nginx-1.16.1.tar.gz nginx.conf#生成容器测试镜像[root@ubuntu2204 1.16.1-alpine]#. build.sh Sending build context to Docker daemon 1.039MBStep 1/10 : FROM alpine-base:v1.2 —> 4f60390e7b99Step 2/10 : LABEL auther=”mooreyxia” version=”1.0″ —> Running in f740a1bd93b4Removing intermediate container f740a1bd93b4 —> 5fe0cd6e2ca0Step 3/10 : ADD nginx-1.16.1.tar.gz /usr/local/src —> ed8ce5d0d3abStep 4/10 : RUN cd /usr/local/src/nginx-1.16.1 && ./configure –prefix=/apps/nginx && make && make install && ln -s /apps/nginx/sbin/nginx /usr/bin/ —> Running in 39f7b1062b2dchecking for OS + Linux 5.15.0-57-generic x86_64checking for C compiler … found + using GNU C compiler + gcc version: 12.2.1 20220924 (Alpine 12.2.1_git20220924-r4) …#查看生成的镜像[root@ubuntu2204 1.16.1-alpine]#docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEnginx-alpine 1.16.1 3421f2c681d8 2 minutes ago 274MB[root@ubuntu2204 1.16.1-alpine]#docker inspect nginx-alpine:1.16.1 [ { “Id”: “sha256:34b3eb9eef98b99d9523ecf66c7dd4def9663123edbc55944a15d36fe11effd7”, “RepoTags”: [ “nginx-alpine:1.16.1” ], “RepoDigests”: [], “Parent”: “sha256:3ec4abd8c21fdeff2613d69454a80c3044cc070b280acce82dc1a3132b219bbd”, “Comment”: “”, “Created”: “2023-01-09T13:33:09.338140302Z”, “Container”: “bd347f3726e2913185bdbcf2363f63b8206fa6e1d6025c062ab73df766ee9603”, “ContainerConfig”: { “Hostname”: “bd347f3726e2”, “Domainname”: “”, “User”: “”, “AttachStdin”: false, “AttachStdout”: false, “AttachStderr”: false, “ExposedPorts”: { “443/tcp”: {}, “80/tcp”: {} }, “Tty”: false, “OpenStdin”: false, “StdinOnce”: false, “Env”: [ “PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin” ], “Cmd”: [ “/bin/sh”, “-c”, “#(nop) “,
“CMD [“nginx”]” ], “Image”: “sha256:3ec4abd8c21fdeff2613d69454a80c3044cc070b280acce82dc1a3132b219bbd”, “Volumes”: null, “WorkingDir”: “”, “Entrypoint”: null, “OnBuild”: null, “Labels”: { “Auther”: “mooreyxia”, “Version”: “v1”, “auther”: “mooreyxia”, “version”: “1.0” } }, “DockerVersion”: “20.10.12”, “Author”: “”, “Config”: { “Hostname”: “”, “Domainname”: “”, “User”: “”, “AttachStdin”: false, “AttachStdout”: false, “AttachStderr”: false, “ExposedPorts”: { “443/tcp”: {}, “80/tcp”: {} }, “Tty”: false, “OpenStdin”: false, “StdinOnce”: false, “Env”: [ “PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin” ], “Cmd”: [ “nginx” ], “Image”: “sha256:3ec4abd8c21fdeff2613d69454a80c3044cc070b280acce82dc1a3132b219bbd”, “Volumes”: null, “WorkingDir”: “”, “Entrypoint”: null, “OnBuild”: null, “Labels”: { “Auther”: “mooreyxia”, “Version”: “v1”, “auther”: “mooreyxia”, “version”: “1.0” } }, “Architecture”: “amd64”, “Os”: “linux”, “Size”: 273518494, “VirtualSize”: 273518494, “GraphDriver”: { “Data”: { “LowerDir”: “/data/docker/overlay2/af7dbf5445243ce62ea3332d6e8de09ac9343507eb09c6bf33af1536d0ea4bb6/diff:/data/docker/overlay2/6c24180effae6e89f5340c4cb97d4d16cf16dc078ea07fd0c83d974a93d29390/diff:/data/docker/overlay2/37d92a852bed1006ae43fbb7370d279f5eac095ded055f1c3da31e114357ad87/diff:/data/docker/overlay2/6aaf443aeea3dfaac29f6d4a9ccad2b90a44cb1f69590ba4028168be32c65f27/diff:/data/docker/overlay2/c43183788449098ba9809d7a8a58666db201daef8eb41ef81e6ce9cbb6818df7/diff:/data/docker/overlay2/31316f8b67f97e6c4a9543dfc4328c0e3c29d52f7dfcdd1af66d66d0c658dcc7/diff:/data/docker/overlay2/4f1e571f109868c90e38a6f0538c5abf9daff5c47ffedb19894d56ce1edd9b37/diff”, “MergedDir”: “/data/docker/overlay2/3719ac9519537c60c9232d10a4f8cf8886795dd10dc77e1419366cf6620a6f4a/merged”, “UpperDir”: “/data/docker/overlay2/3719ac9519537c60c9232d10a4f8cf8886795dd10dc77e1419366cf6620a6f4a/diff”, “WorkDir”: “/data/docker/overlay2/3719ac9519537c60c9232d10a4f8cf8886795dd10dc77e1419366cf6620a6f4a/work” }, “Name”: “overlay2” }, “RootFS”: { “Type”: “layers”, “Layers”: [ “sha256:ded7a220bb058e28ee3254fbba04ca90b679070424424761a53a043b93b612bf”, “sha256:9fa45c5f10892d041fd7f3beb5f6b14ed4183ba367a50684eed57f799b801987”, “sha256:e2dc414ff3be5847f1b2582822d6553ecb69d584e60312681be631f66bd0f1b0”, “sha256:b3dd37fd4cfa61bacb4daba3f3065e295f350c12b92d5636f738287d6398907d”, “sha256:5ccc4c24bcac5474970645ba58d920f5b7a0182d67b60509a146d2e749a081b8”, “sha256:daa344f0fb22b1cf6c567578ef0ae9e2a5796344c5bc487e5fc4119636689d0c”, “sha256:0273e525dd0ac59e7e5400abc8d470a1e801af620eeb9377e9f7ed46c1196318”, “sha256:f54ca93f29a889f6f8e3866d3c48f1380eabffa39d6271b17d1b60cd32914b75” ] }, “Metadata”: { “LastTagTime”: “2023-01-09T21:33:09.357016469+08:00” } }]#生成容器测试镜像[root@ubuntu2204 1.16.1-alpine]#docker run -d -p 8080:80 nginx-alpine:1.16.1 e5d7cda02369cb1cdfdd3108fae99cb39a69210b8b835e5e8f054ee2c7bad5c1[root@ubuntu2204 1.16.1-alpine]#docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESe5d7cda02369 nginx-alpine:1.16.1 “nginx” 6 seconds ago Up 5 seconds 443/tcp, 0.0.0.0:8080->80/tcp, :::8080->80/tcp dreamy_gauss[root@ubuntu2204 1.16.1-alpine]#curl 127.0.0.1:8080Test Page based nginx-alpine[root@ubuntu2204 1.16.1-alpine]#docker exec -it dreamy_gauss sh/ # ps auxPID USER TIME COMMAND 1 root 0:00 nginx: master process nginx 7 nginx 0:00 nginx: worker process 8 root 0:00 sh 15 root 0:00 ps aux
CMD: 容器启动命令
一个容器中需要持续运行的进程一般只有一个,CMD 用来指定启动容器时默认执行的一个命令,且其运行结束后,容器也会停止,所以一般CMD 指定的命令为持续运行且为前台命令.
如果docker run没有指定任何的执行命令或者dockerfile里面也没有ENTRYPOINT命令,那么开启容器时就会使用执行CMD指定的默认的命令
RUN 命令是在构建镜像时执行的命令,注意二者的不同之处
每个 Dockerfile 只能有一条 CMD 命令。如指定了多条,只有最后一条被执行
如果用户启动容器时用 docker run xxx 指定运行的命令,则会覆盖 CMD 指定的命令
案例:
[root@ubuntu2204 1.16.1-alpine]#vim Dockerfile[root@ubuntu2204 1.16.1-alpine]#cat Dockerfile FROM alpine-base:v1.2LABEL author=”mooreyxia” version=”1.0″ ENV NGINX_VERSION 1.16.1ENV NGINX_INSTALL_DIR /apps/nginxADD nginx-1.16.1.tar.gz /usr/local/srcRUN cd /usr/local/src/nginx-${NGINX_VERSION} && ./configure –prefix=${NGINX_INSTALL_DIR} && make && make install && ln -s ${NGINX_INSTALL_DIR}/sbin/nginx /usr/bin/ RUN addgroup -g 2019 -S nginx && adduser -s /sbin/nologin -S -D -u 2019 -G nginx nginxCOPY nginx.conf ${NGINX_INSTALL_DIR}/conf/nginx.confADD index.html /data/nginx/html/index.htmlRUN chown -R nginx.nginx /data/nginx/ ${NGINX_INSTALL_DIR}/EXPOSE 80 443CMD [“${NGINX_INSTALL_DIR}/sbin/nginx”,”-g”,”daemon off”]
我是moore,大家一起加油!这两天打了会PS4,放松一下….文章来源于互联网:47-Docker-Dockerfile镜像创建自动化生产案例