前言

在阅读这篇文章之前,您需要了解docker容器的基本概念,以及容器启动、数据卷等基础知识;
跟文章主要介绍Docker镜像的创建方式,其中Dockerfile创建镜像使用的最多,也最为重要,本博客会重点讲解;
文章最后,附上Dockerfile实践教程,强化对Dockerfile构建镜像的理解;


一、通过快照生成镜像

1.1 案例说明

我们用公共的nginx镜像,把它的默认界面改成hello blog,然后重新推送成镜像,查看是否制作成功

1.2 制作步骤

1、拉取镜像

docker pull nginx:latest

2、运行镜像

docker run --name n1 -itd nginx:latest

3、进入容器

docker exec -it n1 bash

4、修改界面:

cd /usr/share/nginx/html/          
cat > index.html<<EOF
> hello blog
> EOF
cat index.html 
hello blog

5、将容器提交为镜像

docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS     NAMES
432b9375dc46   nginx:latest   "/docker-entrypoint.…"   4 minutes ago   Up 4 minutes   80/tcp    n1

# -a作者信息 -m 镜像信息
docker commit -a 'hyy hahaha' -m 'my nginx image' n1 nginx_hello_blog:v1
sha256:bffe7f45cbcdb1fc2e65a69d8fe53c96cbaedc42a1115a65266a1297895e48bb
docker images '*blog*'
REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
nginx_hello_blog   v1        bffe7f45cbcd   15 seconds ago   161MB

1.3 快照生成镜像的优缺点

优点:

  1. 生成速度快,比较灵活

缺点:

  1. 镜像体积:生成镜像可能包含容器的临时信息
  2. 不易维护:构建历史无法追踪,除了问题不好排查

二、Dockerfile制作镜像

1、 概述

Dockerfile制作镜像,方式是创建一个名为Dockerfile的文件,通过特定语法定义镜像的构建步骤。这种方式优点是构建过程透明,易于排查问题,且有具体的构建脚本(Dockerfile)可以进行版本控制(如git),方便团队协作;

接下来我会讲解Dockerfile的语法和使用,一步步讲透它是如何构建镜像的~~

2、 Dockerfile命令

2.1 CMD 与 ENTRYPOINT

2.2 概述&语法

两者都能指定容器启动时(docker run)需要执行的一个命令。

语法格式:

exec格式:

CMD ["命令", "参数1", "参数2" ,...] 

ENTRYPOINT ["命令", "参数1", "参数2" ,...] 

shell格式:

CMD 命令 参数1 参数2 ... 
ENTRYPOINT 命令 参数1 参数2 ... 
2.3 CMD和ENTRYPOINT的区别

1、 两个指令同时存在时(虽然语法不限制,但CMD建议在后),CMD中的的参数会作为ENTRYPOINT的参数;
实际案例:

Dockerfile:
-------------------------------------------
FROM nginx:latest

WORKDIR /app

COPY ./a.txt ./a/a.txt

COPY ./b.txt ./b/b.txt

ENTRYPOINT ["ls"] #运行的命令

CMD ["-lh", "/app/a"] #ls命令的参数 预期应该显示a.txt
--------------------------------------------
docker build -t n1:v1 .
docker run -it n1:v1 
total 0
-rw-r--r-- 1 root root 0 Feb 27 05:39 a.txt

2、CMD命令会被docker run后面的参数覆盖,但是ENTRYPOINT不会被覆盖。
实际案例:

Dockerfile:
-------------------------------------------
FROM nginx:latest

WORKDIR /app

COPY ./a.txt ./a/a.txt

COPY ./b.txt ./b/b.txt

#后续docker run添加参数,会直接成为ENTRYPOINT(ls)的参数
ENTRYPOINT ["ls"] 

 # 若后续docker run 不添加启动命令,预期应该显示a.txt
CMD ["哈哈我是要被覆盖的参数"] 
--------------------------------------------
docker build -t n1:v1 .
docker run -it --name container_name n1:v1 
total 0
-rw-r--r-- 1 root root 0 Feb 27 05:39 a.txt
docker rm container_name

# CMD命令将被完全覆盖 当前启动命令等价于: ls -lh /app
docker run -it --name container_name n1:v1 -lh /app
total 8.0K
drwxr-xr-x 2 root root 4.0K Feb 27 05:51 a
drwxr-xr-x 2 root root 4.0K Feb 27 05:51 b
2.4 exec/shell格式说明

两种格式,建议用哪个?

先说观点:非特殊情况下,建议统一用exec格式。

原因如下:
shell格式下,docker会自动把CMD后面的命令封装起来,例如这样:/bin/sh -c "我是CMD传递的命令"。这会导致 CMD和ENTERPOINT在传参时很可能出现异常;

我们用一个实际例子演示一下:

Dockerfile:
-------------------------------------------
FROM nginx:latest

WORKDIR /app

COPY ./a.txt ./a/a.txt

COPY ./b.txt ./b/b.txt

ENTRYPOINT ["ls"]

CMD /app/b #预期应该显示b.txt(但实际不会这么显示)
--------------------------------------------

docker build -t n1:v1 .
docker run -it n1:v1 
/bin/sh

/app/b:
b.txt

# 上面显示的内容其实等价于: ls /bin/sh -c "/app/b"
# 我们用一个新的全新的没有entrypoint和cmd的容器演示一下:

Dockerfile:
------------------
FROM nginx:latest

WORKDIR /app

COPY ./a.txt ./a/a.txt

COPY ./b.txt ./b/b.txt
------------------

docker build -t n1:v1 .
docker run -it n1:v1 ls /bin/sh -c "/app/b"
/bin/sh

/app/b:
b.txt

# 可以看到和刚才输出的一摸一样!
2.5 exec格式,如何设置环境变量

exec格式不能像shell格式那样,自动解析$ENV_NAME
但是我们可以通过/bin/sh -c来实现:

Dockerfile:
---------------------
ENTRYPOINT ["sh", "-c", "ls -lh | grep $DIR_NAME"]
---------------------

2.2 FROM

2.2.1 概述

在制作镜像时,指定一个基础镜像,在这个基础上进行制作。
注意点:

  • from指定后,先在本地主机查找指定镜像,没有去远程仓库查找,还是没有,返回构建失败信息;
  • 可以不指定镜像版本,docker默认设置查找版本为latest
  • 一个Dockerfile可以有多个FROM;
  • FROM必须在非注释行或者ARG后的第一个命令
2.2.2 语法
FROM [--platform=<platform>] <image>[:<tag>] [AS <new_name>] 
  • < platform>: 构建的 cpu 架构,如 linux/amd64, linux/arm64
  • < image>: 指定的基础镜像
  • < tag>: 镜像标签(版本)
  • <new_name>: 指定构建步骤的名称,配合COPY --from=<new_name>可以实现多级构建

2.3 ENV

2.3.1 概述

ENV可以设置一个或者多个环境变量,提供给ENV后续的指令(如ENV、ADD、COPY)使用,格式为${evn_name}或$env_name;

注意 :不止镜像构建期间,容器运行时,这些环境变量仍然可以使用!

2.3.2 语法
ENV <key>=<val> ...
2.3.3 演示
Dockerfile:
----------------------
FROM nginx:latest
ENV ENV1="1" ENV2="2"
----------------------

docker build -t n1:v1 .
docker run -itd --name n1 n1:v1
docker exec n1 env | grep ENV
ENV1=1
ENV2=2

2.4 ARG

2.4.1概述

ARG类似ENV,与ENV最大区别是ARG只能在镜像构建阶段使用。

注意:

  • Dockerfile中,可以不指定ARG的val,可以用docker build --build-arg key=val,key-val 指定;
  • ENV和ARG同时存在ENV会覆盖ARG
  • Dockerfile可以包含一个或多个ARG
2.4.2 语法
ARG <name>[=default_name]
2.4.3 演示
Dockerfile:
---------------------------------------------
ARG PYTHON_VERSION
FROM python:${PYTHON_VERSION}-slim

ARG REQUEST_VERSION=2.31.0
RUN pip install requests==${REQUEST_VERSION}
---------------------------------------------
docker build -t python:v1 --build-arg PYTHON_VERSION=3.9 .
[+] Building 0.1s (6/6) FINISHED                                                                                                                            docker:default
 => [internal] load build definition from Dockerfile                                                                                                                  0.0s
 => => transferring dockerfile: 168B                                                                                                                                  0.0s
 => WARN: InvalidDefaultArgInFrom: Default value for ARG python:${PYTHON_VERSION}-slim results in empty or invalid base image name (line 2)                           0.0s
 => [internal] load metadata for docker.io/library/python:3.9-slim                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                                     0.0s
 => => transferring context: 2B                                                                                                                                       0.0s
 => [1/2] FROM docker.io/library/python:3.9-slim                                                                                                                      0.0s
 => CACHED [2/2] RUN pip install requests==2.31.0                                                                                                                     0.0s
 => exporting to image                                                                                                                                                0.0s
 => => exporting layers                                                                                                                                               0.0s
 => => writing image sha256:2f606c91b58880c79fe959b6b3f504889c3fc11f0ae9f1b917c95b67d7809212                                                                          0.0s
 => => naming to docker.io/library/v1:v1                                                                                                                              0.0s

# 如果ARG不设置值,会有警告,所以建议给ARG设置一个默认值

2.5 LABEL

2.5.1 概述

为镜像提供可以阅读的标签(描述该镜像的信息),键值对结构

2.5.2 语法
LABEL <key>=<value> <key>=<value> <key>=<value> ...
2.5.3 演示
Dockerfile:
----------------------------------
FROM ubuntu:latest

LABEL author="hyy" \
      description="up"
----------------------------------

docker build -t linux:v1 .
docker inspect linux:v1 | 过滤"Labels"
"Labels": {
                "author": "hyy",
                "description": "up",
                "org.opencontainers.image.ref.name": "ubuntu",
                "org.opencontainers.image.version": "24.04"
            },

2.6 COPY

2.6.1 概述

功能:把宿主机文件或目录复制到镜像中指定路径

2.6.2 语法:
COPY [chown=<user>:<group>] src1 [src2 src3...] target
  • user和group分别是用户和用户组;
  • src和target可以是文件,也可以是目录;
  • src支持通配符,若src是目录或者通配符,target必须也是目录,并且最后必须/结尾;
  • target不存在会自动创建;
  • target默认为WORKDIR指定的路径,也可以设置为绝对路径;
  • src默认为项目路径,也就是Dockerfile所在路径(非父目录),也可以填写绝对路径;
  • COPY可以搭配--from< name>实现多级构建,本博客专栏后续会分一个主题讲解;
2.6.3 演示

Dockerfile:

----------------------------------------
FROM busybox:latest

WORKDIR /app

# 把test.txt复制到镜像/app路径下,该命为file,并且设置用户/用户组为nobody(busybox镜像自带的一个用户/用户组)
COPY --chown=nobody:nobody ./test.txt ./file.txt

# 把项目路径下所有文件、目录草被到镜像/app/dir中
COPY ./ ./dir/

# 把项目当前目录层级下,所有txt文件(不包含子目录) 和 Dockerfile文件拷贝到镜像/aprent目录下
COPY *.txt Dockerfile /parent/
----------------------------------------

项目结构:

ls -lh
total 16K
drwxr-xr-x 2 root root 4.0K Mar  4 10:07 dir1
-rw-r--r-- 1 root root  135 Mar  4 10:22 Dockerfile
-rwsrwxrwx 1 root root   32 Mar  4 09:48 run.txt
-rws------ 1 root root   15 Mar  4 09:48 test.txt
ls -lh dir1
total 8.0K
-rw-r--r-- 1 root root 2 Mar  4 10:06 a.txt
-rw-r--r-- 1 root root 2 Mar  4 10:07 b.txt

构建镜像+启动容器+查看效果:

docker build -t n1:v1 .
docker run -itd --name n1 n1:v1 
docker exec -it n1 sh
/app # ls -lh
total 8K     
drwxr-xr-x    3 root     root        4.0K Mar  4 02:22 dir
-rws------    1 nobody   nobody        15 Mar  4 01:48 file.txt
/app # ls -lh dir
total 16K    
-rw-r--r--    1 root     root         135 Mar  4 02:22 Dockerfile
drwxr-xr-x    2 root     root        4.0K Mar  4 02:07 dir1
-rwsrwxrwx    1 root     root          32 Mar  4 01:48 run.txt
-rws------    1 root     root          15 Mar  4 01:48 test.txt
/app # ls -lh ../parent/
total 12K    
-rw-r--r--    1 root     root         135 Mar  4 02:22 Dockerfile
-rwsrwxrwx    1 root     root          32 Mar  4 01:48 run.txt
-rws------    1 root     root          15 Mar  4 01:48 test.txt

2.7 ADD

2.7.1 概述

功能:
和COPY基本无差别,但是ADD更“智能”,针对特殊压缩文件如tar文件和url它可以自动解压、下载到镜像指定目录;
局限性:
ADD虽然看上去更“智能”,但是不推荐用,特别是一些需要解压、或者资源下载的场景。因为它的“智能”,很有限,只能针对tar文件解压,别的类型比如gz,无法进行压缩。
此外下载和解压这个操作不够透明,不利于问题排查;

2.7.2 语法
COPY [--chown=<user>:<group>] src1 [src2 src3 ...] target
  • < src>:要复制的源文件、url、压缩文件、文件路径
  • < target>:目标路径
    -< user>和< group>: --chown更改用户组,分别是所属用户和用户组
2.7.3 演示
2.7.3.1 案例介绍
  1. dockerfile添加一个显示版本号的环境变量
  2. dockerfile指定工作目录
  3. dockerfile配置一个COPY(复制普通文件)和ADD(自动解压一个文件)对比演示
  4. 通过简单的python脚本,查看环境变量、copy的文件和解压的文件
2.7.3.2 效果演示

项目结构:

ls -lh
total 16K
-rw-r--r-- 1 root root 889 Mar  4 11:00 app.py
-rw-r--r-- 1 root root 134 Mar  4 10:54 Dockerfile
-rw-r--r-- 1 root root  34 Mar  4 10:52 file.tar
-rw-r--r-- 1 root root   5 Mar  4 10:52 haha.txt

关于tar归档文件的获取:

  1. 可以通过tar -zcvf file.tar file.txt把file.txt归档并且压缩(linux自带)
  2. -z是压缩,默认归档后不压缩(底层用gzip命令进行压缩,也是linux原生支持的);
  3. -c是创建一个归档文件;
  4. -v是查看详细执行过程;
  5. -f是指定文件名称

Dockerfile:

FROM python:3.9-slim

ENV MY_VERSION=V1.1

WORKDIR /app

COPY ./haha.txt ./haha.txt

ADD ./file.tar ./

#通过CMD容器启动时,执行python app.py脚本,查看容器构建效果
CMD ["python", "app.py"]

app.py:

import os
import sys


print(">>> 容器应用正在启动...")

# 1. 演示 ENV (读取环境变量)
env_val = os.getenv("MY_VERSION", "未知")
print(f"[ENV 演示] 当前应用版本: {env_val}")

# 2. 演示 WORKDIR (显示当前路径) get current work dir(getcwd)
print(f"[WORKDIR 演示] 当前工作目录: {os.getcwd()}") 

# 3. 演示 COPY (读取普通文件)
try:
    with open("haha.txt", "r") as f:
        print(f"[COPY 演示] 读取 haha.txt: {f.read().strip()}")
except:
    print("[COPY 演示] 失败:找不到 haha.txt")

# 4. 演示 ADD (读取自动解压的文件)
try:
    # 注意:ADD 解压后会保留目录结构
    with open("file.txt", "r") as f:
        print(f"[ADD 演示]  读取解压文件: {f.read().strip()}")
except FileNotFoundError:
    print("[ADD 演示]  失败:文件没解压成功")

print(">>> 应用执行完毕,准备退出。")
print("="*30)

启动容器,查看效果:

docker build -t py1:v1 .
docker run -it --name p1 py1:v1 
==============================
>>> 容器应用正在启动...
[ENV 演示] 当前应用版本: V1.1
[WORKDIR 演示] 当前工作目录: /app
[COPY 演示] 读取 haha.txt: haha
[ADD 演示]  读取解压文件: I`m file.tar
==============================

2.8 VOLUME

2.8.1 概述

功能:
指定镜像中的一个挂载点(挂载到宿主机),类型是匿名卷(不指定名字,名字由docker自行赋予的管理卷)

小提示:
在现代项目中,一般不通过Dockerfile指定挂载点,只是用来避免程序员忘记这回事,声明一下某个路径需要挂载到宿主机;
因为它无法灵活指定关在类型,只能挂载匿名卷;最佳实践是在docker-compose.yml中配置

2.8.2 语法:
VOLUME ["镜像中需要挂载的路径"]

注意:

  1. 若docker run -v设置了同一个路径的挂载,那么Dockfile将不会创建对应匿名卷;
  2. docker rm删除容器后,挂载点不会被删除;
  3. 若容器中指定路劲已有数据,这些数据会自动覆盖到宿主机匿名卷中;
2.8.3 演示

项目结构:

ls -lh
total 8.0K
-rw-r--r-- 1 root root 184 Mar  4 14:58 Dockerfile
-rw-r--r-- 1 root root 273 Mar  4 14:55 init.sql

Dockerfile

FROM mysql:5.7 #设置基础镜像

# 设置维护人信息,可选
LABEL maintainer="hyy@blog.cn"

# 显式声明匿名卷,用于保存自定义日志等
VOLUME ["/var/log/mysql"]

#设置mysql密码,不建议dockerfile中配置,可以docker run的时候配置密码
ENV MYSQL_ROOT_PASSWORD=111

# 设置初始化表数据的sql,docker-entrypoint-initdb.d/是mysql镜像官方指定的数据
COPY ./init.sql /docker-entrypoint-initdb.d/

#官方默认的启动命令,不加也可以
CMD ["mysqld"]

init.sql:

CREATE DATABASE IF NOT EXISTS my_test_db; --创建一个数据库
USE my_test_db;
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); --创建一个user表
INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('DockerMaster'); #插入三条数据

启动容器,查看效果:

docker volume ls 
DRIVER    VOLUME NAME

docker build -t n1:v1 .
docker run --name n1 -e MYSQL_ROOT_PASSWORD=222 -itd n1:v1
docker volume ls
DRIVER    VOLUME NAME
local     462ae60a04a1e5d6ccf40cc6cea0dcf7fcc24907e869a37c29b119ec90bf5597

# 下面这个路径是我们宿主机挂载的数据库my_test_db的元数据
ls -lh /var/lib/docker/volumes/462ae60a04a1e5d6ccf40cc6cea0dcf7fcc24907e869a37c29b119ec90bf5597/_data/my_test_db
total 112K
-rw-r----- 1 lxd docker   65 Mar  4 15:08 db.opt
-rw-r----- 1 lxd docker 8.5K Mar  4 15:08 users.frm
-rw-r----- 1 lxd docker  96K Mar  4 15:08 users.ibd

2.9 HEALTCHECK

2.9.1 概述

用于检测容器是否正常工作;
Dockerfile的HEALTCHECK和docker-compose的healtcheck是等价的,只是docker-compose优先级更大,如果两个地方都配置了健康检查,则docker-compose的健康检查会覆盖Dockerfile。

2.9.1 语法
#通过command命令,检查容器是否健康,健康,返回0
HEALTCHECK [options] CMD command 

#禁用来自基础镜像的健康检查
HEALTCHECK NONE 

options(和docker-compose的配置是一样的):

  • –start-period=等待启动时间(默认0s):等容器启动后指定时间后,再进行探测检查,给它一个缓冲时间,避免无效检查;
  • interval=延迟时间(默认30s):间隔30s检查一次;
  • timeout=超时时间(默认30s):超时后,认为这次检查容器不健康(返回1);
  • retries=重试次数(默认3此):重试指定次数后,仍然不健康,直接启动报错;
2.9.2 演示

Dockerfile:

#官方nginx镜像,基于debain系统
FROM nginx:latest 

# 替换为阿里云 Debian(Nginx容器用的debian) 镜像源(解决国内下载慢/失败问题)---直接拷贝过来就行
RUN echo "deb http://mirrors.aliyun.com/debian/ bookworm main non-free contrib" > /etc/apt/sources.list && \
    echo "deb http://mirrors.aliyun.com/debian/ bookworm-updates main non-free contrib" >> /etc/apt/sources.list && \
    echo "deb http://mirrors.aliyun.com/debian/ bookworm-backports main non-free contrib" >> /etc/apt/sources.list && \
    echo "deb-src http://mirrors.aliyun.com/debian/ bookworm main non-free contrib" >> /etc/apt/sources.list && \
    echo "deb-src http://mirrors.aliyun.com/debian/ bookworm-updates main non-free contrib" >> /etc/apt/sources.list && \
    echo "deb-src http://mirrors.aliyun.com/debian/ bookworm-backports main non-free contrib" >> /etc/apt/sources.list && \
    echo "deb http://mirrors.aliyun.com/debian-security/ bookworm-security main non-free contrib" >> /etc/apt/sources.list && \
    echo "deb-src http://mirrors.aliyun.com/debian-security/ bookworm-security main non-free contrib" >> /etc/apt/sources.list

# 替换 Debian 官方源为阿里云源(解决超时问题),并给nginx容器安装 curl
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources 2>/dev/null || true && \
    apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*


# 可写可不写:声明 Nginx 默认端口
EXPOSE 8080

# 启动 Nginx(前台运行,保证容器不退出)
CMD ["nginx", "-g", "daemon off;"]

# 用curl这个命令进行健康检查,理论上9090端口会报错,所以一段时间后,启动的容器会显示unhealthy
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
  CMD curl -fs 127.0.0.1:9090 || exit 1

构建/启动容器,查看效果:

docker build --no-cache  -t n1:v1 .
docker run --name n1 -itd n1:v1 

#大概20s后
docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS                          PORTS              NAMES
f3362f7cc128   n1:v1     "/docker-entrypoint.…"   About a minute ago   Up About a minute (unhealthy)   80/tcp, 9090/tcp   n1

2.10 USER

2.10.1 概述

用于指定运行镜像时,以及在其后的 RUN、CMD 和 ENTRYPOINT 指令中使用的用户名(或 UID)和可选的用户组(或 GID)。

在 Docker 中,容器默认以 root 权限运行。为了系统安全(遵循“最小权限原则”),生产环境中强烈建议创建一个非特权用户,并使用 USER 指令切换到该用户来运行应用程序,以防止容器内的恶意进程通过漏洞获取宿主机的最高权限。

2.10.2 语法
# 通过用户名和(可选的)用户组指定
USER <user>[:<group>]

# 通过 UID 和(可选的)GID 指定(推荐做法,更精确)
USER <UID>[:<GID>]

注意:

  • 在使用 USER 切换用户之前,该用户或 UID 必须已经存在于镜像中(通常需要先用 RUN 命令通过 useradd 创建)。
  • 如果只指定了用户或 UID,而没有指定用户组,则默认使用 root 组或该用户的默认主组。
2.10.3 使用场景
  • 提升安全性:防止应用漏洞导致提权,限制进程只能访问特定的资源。
  • 满足平台合规要求:许多企业级容器编排平台(如 Kubernetes 的 PodSecurityPolicies、OpenShift 等)强制要求容器不能以 root 身份运行。
  • 文件权限隔离:限制应用程序只能读写它自己专属的目录,避免误操作或恶意修改基础环境文件。
2.10.4 演示

Dockerfile:

# 基于 alpine 镜像
FROM alpine:latest

# 1. 创建一个名为 appuser 的普通用户和名为 appgroup 的组
# alpine 中使用 addgroup 和 adduser
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 2. 创建应用工作目录
WORKDIR /app

# 3. 将 /app 目录的所有权交给 appuser(重要!否则切换用户后可能无法写入该目录)
RUN chown -R appuser:appgroup /app

# 4. 切换到非 root 用户 appuser
USER appuser

# 5. 验证:启动容器时执行命令查看当前用户
CMD ["whoami"]


小提示:权限与用户的那些事儿
在编写 Dockerfile 并尝试切换非 root 用户时,我们经常会用到 adduser -S 和 chown -R,它们背后的含义如下:
1. adduser -S 中的 -S (System)
它代表创建一个 “系统用户” 。系统用户具有以下三个特点,完美契合 Docker 的“最小权限”安全理念:

  • 专为运行程序而生:它专门用来在后台运行应用程序(如 Java/Node.js 服务),而不是给真实人类交互使用的。
  • 被剥夺登录权限:它默认没有密码,也没有正常的终端 Shell 环境,这意味着黑客即使拿到该账号也无法登录系统。
  • 专属低权 ID:系统会为其分配一个专属的较小 UID(通常小于 1000),与普通用户的 ID 区分开,严格限制其系统级特权。

2. chown -R 中的 -R (Recursive)
它代表 “递归” 操作。


构建/启动容器,查看效果:

# 1. 构建镜像
docker build -t user-test:v1 .

# 2. 运行容器(--rm 表示运行结束后自动删除容器),查看输出
docker run --rm user-test:v1

# 输出结果:
appuser

(说明:控制台输出结果为 appuser 而不是 root,证明后续的指令和容器启动进程都已降级为普通用户执行,达到了安全隔离的目的。)

更多推荐