9、dockerfile

dockerfile

本章要点:dockek-Dockerfile基础用法

镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是Dockerfile

Dockerfile 是什么?

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建

  • 从哪开始构建(FROM)
  • 维护信息声明(LABEL)
  • 要做哪些修改(RUN / COPY / ADD / ENV)
  • 最终如何启动(CMD / ENTRYPOINT / EXPOSE)

Dockerfile目录说明

一般运行在顶层目录,下面包含子目录以及一个Dockerfile文件, 这个文件名一定是大写, 如/data/docker 下包含 (Dockerfile)类型文件, (nginx, tomcat)类型目录, 过滤文件 .dockeringore 写在这里头的都不会被打包

bash 复制代码
]# pwd
/data/build/nginx
nginx]# ls -la
    Dockerfile      <-- 打包文件
    .dockeringore   <-- 过滤哪些文件或目录
    otherdirs       <-- 由COPY/ADD/FILE 之类的参数引用,files相同 
    otherfiles

Dockerfile参数

FROM

指定基础镜像,必须为第一个命令

bash 复制代码
格式:
  FROM <image>
  FROM <image>:<tag>
  FROM <image>@<digest>

示例:  FROM mysql:5.6
注:   tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像
注:    如果指定的镜像没有 就会从dockerhub上下载
bash 复制代码
]# cat Dockerfile 
# then first dockerfile   # 第一行可以是注释行
FROM busybox:latest       # 注意FROM等关键字都是大写

]#   docker build -t test:v0.0.1 .   # 构建镜像, 名称为 test:v0.0.1 .是表示当前的Dockerfile
Sending build context to Docker daemon  2.048kB
Step 1/1 : FROM busybox:latest
 ---> 020584afccce
Successfully built 020584afccce
Successfully tagged test:v0.0.1

LABEL

MAINTAINER已替换化 LABEL 用于为镜像添加元数据

bash 复制代码
格式:
    LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:
  LABEL version="1.0" description="test" by="TEST"
注:
  使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。
bash 复制代码
]# cat Dockerfile 
FROM  busybox:latest
LABEL maintainer='xiong' desction="test container"

]# docker build -t test:v0.0.2 .
....
]# docker image inspect -f {{.ContainerConfig.Labels}} test:v0.0.2
            "Labels": {
                "desction": "test container",
                "maintainer": "xiong"
            }

COPY

功能类似ADD,但是是不会自动解压文件,也不能访问网络资源, 使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git进行管理的时候,

bash 复制代码
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]

比如:COPY package.json /usr/src/app/
也可以是多个,甚至是通配符
    COPY home* /usr/src/app/
    COPY hom?.txt /usr/src/app/

文件复制准则

  • 必须是build上下文中的路径,不能是其父目录中的文件
  • 如果是目录, 则其内部文件或子目录会被递归复制,但目录自身不会被复制
  • 如果指定了多个,或在中使用了通配符,则必须是一个目录,且必须以/结尾
  • 如果事先不存在,它将会被自动创建,这包括其父目录路径
bash 复制代码
]# cat Dockerfile 
From busybox:latest
LABEL desction='test_v0.0.3'

COPY index.html /data/html/   # 将index.html复制到 /data/html/下
COPY in?.php /data/html/      # 也可以使用正则表达式  ?表示一个字符
COPY 1.txt 2.txt /data/html/  # 如果是多个源文件则 目标目录必须是以 / 结尾

COPY test/a/* /opt/a   # 假设a目录为空,或a目录下的目录为空,会提示目录不能为空的异常

ADD

​ ADD 指令和 COPY 的格式和性质基本一致,将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget

bash 复制代码
格式:
    ADD <src>... <dest>
    ADD ["<src>",... "<dest>"] 用于支持包含空格的路径
示例:
    ADD hom?.txt /mydir/                   # ? 替代一个单字符,例如:"home.txt"
    ADD xx.tar.gz /data/src/     		   # 将会自动将xx.tar.gz解压到 /data/src/目录下
    ADD http://xx.com/xx.tar.gz /data/src  # 下载xx.tar.gz到/data/src目录下不会解压
bash 复制代码
]# cat Dockerfile 
From busybox:latest

LABEL desction='test_v0.0.4'

# 查看区别, 如果是从服务器下载,那么就相当于是wget
ADD http://nginx.org/download/nginx-1.17.6.tar.gz /data/src/
ADD nginx-1.17.6.tar.gz /data/src/      # 直接复制文件就是tar解压

# 将a.txt 设置为644  b.txt设置为777
COPY a.txt /data/    # 容器权限也会保持一致
ADD  b.txt /data/     # 容器权限也会保持一致
ADD  adir /data/     # adir为空

]# docker run -it --rm 4dd   # 随便运行一个
root@3bdeb5eb1065:/data# ls -l
total 8
-rw-r--r-- 1 root root  6 Oct 20 08:50 a.txt    # 权限一样
-rwxrwxrwx 1 root root  9 Oct 20 08:50 b.txt
drwxr-xr-x 2 root root 33 Oct 20 08:48 src     
drwxr-xr-x 3 root root 26 Oct 20 08:48 src2

root@3bdeb5eb1065:/data# ls src     # ADD 如果不是本地则就直接下载
nginx-1.17.6.tar.gz

root@3bdeb5eb1065:/data# ls src2/nginx-1.17.6/   # 如果是本地它会解压
CHANGES  CHANGES.ru  LICENSE  README  auto  conf  configure  contrib  html  man  src

WORKDIR

​ 指定工作目录 ,以后各层的当前目录就被改为指定的目录,如该目录不存在, WORKDIR 会帮你建立目录

bash 复制代码
格式:
    WORKDIR /path/to/workdir
示例:
    WORKDIR /a  (这时工作目录为/a)
    WORKDIR b  (这时工作目录为/a/b)
    WORKDIR c  (这时工作目录为/a/b/c)
bash 复制代码
]# cat Dockerfile 
FROM busybox:latest
LABEL desction='test:v0.0.7'
WORKDIR /data/src/           # 第一次设置工作目录
COPY file1.txt ./  			 # 将文件复制到工作目录中 
WORKDIR ./file/              # 第二次设置工作目录并在反面跟随file目录
ADD nginx-1.17.6.tar.gz ./   # 将其解压至 /data/src/file/目录中

]# docker run -it --rm test:v0.0.7     # build之后 运行容器查看
root@ef2a7b81b606:/data/src/file # ls  # 进入之后工作目录就是 /data/src/file
nginx-1.17.6                           # nginx文件也会正常被解压
/data/src/file # cd ..
/data/src # ls -l                    # 而第一次设置的目录也复制正常
total 4 
drwxr-xr-x    1 root     root   25 Nov 20 06:55 file
-rw-r--r--    1 root     root   5 Nov 20 06:55 file1.txt

# 只需要注意: 如果第一次设置,第二次用相对路径./file那就是 A+B路径, 如果都是绝对路径那就是 C路径
# 如 A: /data/src   B: ./file = /data/src/file    C: /file  = /file 而不是A+B+C

VOLUME

bash 复制代码
格式:  # 用于指定持久化目录
    VOLUME ["/path/to/dir"]
示例:
    VOLUME ["/data"]
    VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"

一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:

  1. 卷可以容器间共享和重用
  2. 容器并不一定要和其它容器共享卷
  3. 修改卷后会立即生效
  4. 对卷的修改不会对镜像产生影响
  5. 卷会一直存在,直到没有任何容器在使用它
bash 复制代码
]# cat Dockerfile 
FROM busybox:latest

LABEL desctions='test:v0.0.8'
VOLUME /data/docker/volume/   
# 将这个目录持久化,  这样持久方式是 docker-manage管理, 主机持久化目录是随机的
# build 然后  ]# docker run -it --rm --name file1 test:v0.0.8
]# docker image inspect test:v0.0.8
        "Mounts": [
            {"Type": "volume",
                "Name": "xxxxxx",
                "Source": "/var/lib/docker/volumes/xxxxxx/_data",
                "Destination": "/data/docker/volume",
                "Driver": "local",
                ....
            }
            
# 容器共享卷组还是得使用 --volumes-from
]# docker run -it --rm --volumes-from file1 --name file2 test:v0.0.8

# 此时在创建第三个  
~]# docker run -it --rm --name file3 test:v0.0.8  # 挂载点不变, 但与前两个不在同Local目录中

ENV

指定环境变量,在镜像生成过程中会被后续RUN指令使用, 后续运行容器时该环境变量也会生效。

复制代码
格式:
    ENV <key> <value>  
    ENV <key>=<value> ...  
示例:
    ENV myName John         
    ENV myDog Rex The Dog
    ENV myCat=fluffy cat2=cat2
   
#<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
#可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行
bash 复制代码
]#  cat Dockerfile 
FROM ubuntu
LABEL version="v1.0.2" auth="xiong" time="20251020"

WORKDIR /data/src
ADD nginx-1.17.6.tar.gz ./

ENV NGINX_HOME="/data/src/nginx-1.17.6"

]#  docker build -t testu1:v1.0.7 .

]# docker inspect 445
"Env": [
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    "NGINX_HOME=/data/src/nginx-1.17.6"   <-- 通过inspect查看详细标签
],

RUN

构建镜像时执行的命令

bash 复制代码
RUN用于在镜像容器中执行命令,其有以下两种命令执行方式:
shell执行
格式:
    RUN <command>
exec执行
格式:
    RUN ["executable", "param1", "param2"]
示例:
    RUN yum -y install nginx \
        yum clean all
    RUN apk update
    RUN ["/etc/execfile", "arg1", "arg1"]
    # 如果想运行通配符之类的命令则需要使用  CMD ['/bin/bash','-c','xxx']
注:
  RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache

run与cmd的区别: run是运行在build之时,而cmd是在运行容器的时候执行

bash 复制代码
]# cat Dockerfile     
FROM ubuntu:latest

LABEL desction="test_v0.0.9" command="run"

RUN apt-get update \                     # RUN在build的时候就会直接执行。
    && apt-get -y install nginx \        # 安装nginx
    && apt-get clean                     # 注意在每一层安装好文件一定要清除相关的文件

CMD

​ CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令

bash 复制代码
格式:   # 如果运行了多个CMD 最终执行的命令只会是最后一个有效
    CMD ["executable","param1","param2"] (执行可执行文件,优先)
    CMD command param1 param2 (执行shell内部命令)
    # 前两种语法格式的意义同RUN
    CMD ["param1","param2"] (设置了 ENTRYPOINT ,则直接调用ENTRYPOINT添加参数)
示例:
    CMD echo "This is a test." | wc -
    CMD ["/usr/bin/wc","--help"]
  1. env + cmd

    bash 复制代码
    ]#  cat Dockerfile 
    FROM test:v0.0.9     # 这个镜像是在运行run之后的....
    LABEL desction='test:v0.1.0' command="cmd"
    ENV NGINX_ENV="/usr/sbin/nginx"
    CMD  ${NGINX_ENV} -g "daemon off;"    # 让nginx运行在前台
    
    ]# docker image inspect test:v0.1.0
    "Env": [
        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "NGINX_ENV=/usr/sbin/nginx"
    ],
    "Cmd": [  "/bin/sh", "-c", "${NGINX_ENV} -g daemon off;" ],
    
    cat Dockerfile    # CMD使用方式二,  推荐使用这种,看起来更加简洁
    	CMD ['/usr/sbin/nginx','-g','daemon off;']  # 使用列表方式
    	
    # 查看容器cmd启动文件,  注意每次运行的时候 cmd都会自动加上 /bin/sh -c
    "Cmd": [ "/bin/sh","-c","['/usr/sbin/nginx','-g','daemon off;']"],
    
    # 注意点:  如果在运行容器的时候后面在跟随命令,将会覆盖镜像定义的cmd
    ]# docker run -it --rm --name ng1 test:v0.1.1 ls /bin/  
  2. run + cmd

    bash 复制代码
    ]# cat Dockerfile
    FROM test:v0.0.9
    
    LABEL desction='test:v0.1.2' command="cmd"
    ENV NGINX_ENV="/usr/sbin/nginx"
    RUN mkdir /data/html -p && \       # 构建镜像时运行
        echo '<h1> welcome use docker </h1>' > /data/html/index.html
    
    # 注意如果这么写列表是取不到变量的,如果想要用变量就得加上前缀 /bin/sh  -c
    # "Cmd": ["${NGINX_ENV}","-g", "daemon off;" ],     无法取到shell,如果有变量推荐使用第一种
    CMD ${NGINX_ENV} -g "daemon off;"   # 带变量还是使用这种,让程序自动解析

ENTRYPOINT

​ 配置容器,使其可执行化。配合CMD可省去"application",只使用参数。

​ ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令

bash 复制代码
格式:
    ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)
    ENTRYPOINT command param1 param2 (shell内部命令)
示例:
    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]

# 当同时定义CMD跟ENTRYPOINT时,CMD会被当成参数传给ENTRYPOINT

EXPOSE

​ EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口,

复制代码
格式:
    EXPOSE <port> [<port>...]
示例:
    EXPOSE 80 443
    EXPOSE 11211/tcp 11211/udp

USER

​ 指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户

bash 复制代码
格式:
   USER user
  USER user:group
  USER uid
  USER uid:gid
  USER user:gid
  USER uid:group

示例:
  USER www

HEALTHCHECK

HEALTHCHECK 支持下列选项:

--interval=<间隔> :两次健康检查的间隔,默认为 30 秒;

--timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;

--retries=<次数> :当连续失败指定次数后,则将容器状态视为 unhealthy ,默认 3次。

bash 复制代码
空目录下创建一个Dockerfile
FROM centos

COPY nginx.repo /etc/yum.repos.d/
RUN groupadd -g 1000 nginx \
    && useradd -g 1000 -u 1001 nginx \
    && yum -y install nginx 

# 每10秒检查一次,超时时长5秒就退出
HEALTHCHECK --interval=10s --timeout=5s \
  CMD curl -fs http://127.0.0.1 || exit 1
EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

构建并运行并查看状态,一开始是healthy状态,然后理性成starting说明就启动了,失效了就会改变成 unhealthy状态
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS                    NAMES
e7982e3ec394        nginx:v3            "nginx -g 'daemon of..."   2 seconds ago       Up 2 seconds (health: starting)   0.0.0.0:80->80/tcp       Tnginx

最后可以使用 docker inspect 查看容器的状态 使用python json模块打印
docker inspect -f '{{json .State.Health}}' Tnginx | python -m json.tool
    "Status": "healthy"

ARG

用于指定传递给构建运行时的变量

BASH 复制代码
格式:
    ARG <name>[=<default value>]
示例:
    ARG site
    ARG build_user=www

ONBUILD

​ ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN , COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行,Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的

.dockerignore

bash 复制代码
# 忽略匹配模式路径下的文件
# file_dir
	*/temp*
	*/*/temp*
	tmp?
	~*

总结镜像生成

  • 精简镜像用途

  • 选用合适的基础镜像, 书上推荐使用(debian), 这里我一般使用centos

    bash 复制代码
    # 从下载上来看, debian的确是比centos要小近一半
    ubuntu       latest    7e0aa2d69a15   12 days ago    72.7MB
    debian       latest    0d587dfbc4f4   3 weeks ago    114MB
    centos       latest    300e315adb2f   4 months ago   209MB
  • 提供足够清晰的命令注释和维护者信息

  • 正确使用版本号:

  • 减少镜像层数: 如多个RUN可以合并为一个

  • 及时删除临时和缓存文件: 如apt-get, /var/cache/apt下会缓存一些安装包

  • 提高生成速度: 可利用 .dockerignore 文件指定

  • 调整合理的指令顺序,内容不变的尽量放在前面,这样可尽量利用

  • 减少外部源的干预,如果确实要从外部引入数据,需要指定持久的地址,并带有版本信息

实例

nginx

  1. 准备基础nginx包

    bash 复制代码
    # 注意,下载的包是源文件,需要先本机build一下
    ]# wget http://nginx.org/download/nginx-1.28.0.tar.gz
    ]# tar xf nginx-1.28.0.tar.gz
    ]# cd nginx-1.28.0
    ]# ./configure --prefix=/data/nginx
    ]# make -j 32 && make -j 32  install     # 有多少核CPU写多少数字
    ]# cd /data/ && tar zcf nginx-1.28.0.tar.gz nginx     这样基础就准备好了
  2. nginx.conf

    bash 复制代码
    user  root;
    events {
        worker_connections  102400;
    }
    http {
        include       mime.types;
        default_type  application/octet-stream;
        server {
            listen 9999;
            location / {
                root /data/web/;
                index index.html;
            }
        }
    }
    # 临时用,如果想一次构建多次用,记得用include xx/conf.d
  3. Dockerfile

    bash 复制代码
    nginx]# cat Dockerfile 
    FROM ubuntu
    
    LABEL version="1.28.0" auth="test"
    
    ADD nginx-1.28.0.tar.gz /data/
    COPY nginx.conf /data/nginx/conf/
    
    RUN mkdir /data/web && \
        echo '<div> hello world </div>' >> /data/web/index.html
    
    ENV NGINX_HOME="/data/nginx"
    EXPOSE 9999
    
    CMD ["/data/nginx/sbin/nginx","-g","daemon off;"]
  4. 运行并访问

    bash 复制代码
    nginx]# docker build -t nginx:1.28.0 .
    nginx]# docker run -d -P --name n1 nginx:1.28.0
    nginx]# curl http://127.0.0.1:10243    <-- 用 docker ps看一下映射的端口并访问
    <div> hello world </div>

ssh

没啥意义,就是测着玩

  • 准备文件

    bash 复制代码
    docker-ssh]# ssh-keygen -t rsa
    docker-ssh]# cat /root/.ssh/id_rsa.pub > authorized_keys
  • Dockerfile

    bash 复制代码
    docker-ssh]# cat Dockerfile 
    FROM ubuntu
    
    RUN apt-get update \
        && apt-get install -y openssh-server \
        && mkdir -p /var/run/sshd \ 
        && mkdir -p /root/.ssh \ 
        && sed -i "s@session    required     pam_loginuid.so@#session    required     pam_loginuid.so@gi" /etc/pam.d/sshd \
        && chmod 755 /run.sh \
        && rm -rf /var/cache/apt/*
    
    ADD authorized_keys /root/.ssh/authorized_keys
    
    EXPOSE 22
    
    CMD ["/usr/sbin/sshd","-D"]
  • 运行

    bash 复制代码
    # 运行并连接,个人是不太推荐使用这种,一个容器就跑一个服务(docker理念), 用docker exec也能满足
    docker-ssh]# docker run -itd -p 11122:22 --name s1 sshd:test
    docker-ssh]# ssh 192.168.9.234 -p 11122
    root@43405da61e28:~#  
相关推荐
panplan.top6 小时前
Tornado + Motor 微服务架构(Docker + 测试 + Kubernetes)
linux·python·docker·微服务·k8s·tornado
-指短琴长-6 小时前
Docker基础【Ubuntu安装/Windows安装】
windows·ubuntu·docker
Z_Xshan7 小时前
docker 容器web站点 中文文件名访问404问题
linux·开发语言·docker
回忆是昨天里的海8 小时前
k8s集群-节点间通信之安装kube-flannel插件
java·docker·kubernetes
ZHE|张恒9 小时前
Docker 安装 MinIO(20250422)
运维·docker·容器
LCG元10 小时前
Docker容器化实战:将你的SpringBoot应用一键打包部署(二)-设置CI/CD流水线实现自动化部署
后端·docker
袁煦丞 cpolar内网穿透实验室11 小时前
N1+iStoreOS+cpolarN1盒子变身2048服务器:cpolar内网穿透实验室第653个成功挑战
运维·服务器·docker·远程工作·内网穿透·cpolar
王中阳Go背后的男人13 小时前
Docker磁盘满了?这样清理高效又安全
后端·docker
王中阳Go13 小时前
Docker磁盘满了?这样清理高效又安全
docker