1.Dockerfile简介
Dockerfile是一个包含了一系列命令的文本文件,这些命令可以用于自动化地创建一个Docker镜像。通过编写Dockerfile,可以将环境配置、应用程序代码、依赖关系等打包成一个镜像,便于快速创建容器。
用户可以将自己的应用打包成镜像,从而让应用在容器中运行。还可以对官方镜像进行扩展,打包成适合生产环境的应用镜像。
2.Dockerfile常用指令
2.1 FROM:指定基础镜像
指定构建新镜像时使用的基础镜像,通常必须是Dockerfile的第一个有效指令
# 格式
FROM <image:[版本标签]>
# 指定基础镜像
FROM centos:7
FROM ubuntu:22.04
# 说明:如果不指定版本标签,默认使用latest
2.2 LABEl:添加镜像的元数据
LABEL一般使用键值对方式。
# 格式
LABEL <key>=<value> <key>=<value>....
# 为镜像添加元数据
LABEL version="2.0" description="业务系统第二个版本"
# 说明:一条LABEL可以指定多条元数据,尽量不要写多个LABEL
2.3 RUN:构建镜像时执行的命令
有两种执行方式
方式一:shell执行
# 格式
RUN <command>
# 示例
RUN yum update -y
# 说明:RUN 指令创建的中间镜像会被缓存,并在下次构建中使用。可以在构建时使用--no-cache取消缓存:
docker build --no-cache
方式二:exec执行
# 格式:
RUN ["executable", "参数1", "参数2"]
# 示例
RUN ["/bin/bash", "-c", "echo hello"]
2.4 ENV:在容器内部设置环境变量
# 格式
ENV <key> <value>
# 示例
ENV MYPATH /usr/local
2.5 ADD:将本地文件添加到镜像中
相当于scp。只是scp 需要将用户名和密码的权限验证,而ADD 不用。
tar 类型文件会自动解压,可以访问网络资源,类似 wget
# 语法
ADD <src> <dest>
ADD ["src", "dest"]
# 示例
ADD nginx.tar /opt
# 说明:路径可以是相对路径或绝对路径,建议使用绝对路径。
# 尽量不要把<src> 写成一个文件夹,如果<src> 是一个文件夹,复制整个目录的内容
2.6 COPY:将文件或目录复制到镜像中。
功能类似 ADD,但不会自动解压文件,也不能访问网络资源
# 语法
COPY <src> <dest>
COPY ["<src>", "<dest>"]
与 ADD 区别,COPY 的只能是本地文件,其他用法一致
2.7 VOLUME:指定持久化目录
VOLUME 指令用于在镜像中创建一个挂载点(mount point),并且声明该挂载点应该被 Docker 主机或其他容器共享的卷(volume)覆盖。
VOLUME 指令的主要目的是为了在多个容器之间共享数据,或者为了持久化数据(即使容器被删除,数据仍然保留在宿主机上)。
# 格式
VOLUME ["/path/to/dir"]
或者
VOLUME /path/to/dir
# 示例
VOLUME ["/data"]
VOLUME ["/data/log", "/data/config"]
# 一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统
# /data 是容器内的一个目录路径,Docker 会在这个路径上创建一个挂载点。
容器使用的是AUFS(联合文件系统),这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失。所以如果想要数据持久化,就得使用VOLUME
功能说明:卷可以在容器间共享和重用;修改卷后会立即生效;对卷的修改不会对镜像产生影响
示例 1:持久化 MySQL 数据
假设你有一个用于运行 MySQL 容器的 Dockerfile,你希望将 MySQL 的数据存储目录声明为卷,以便数据持久化。
FROM mysql:8.0
# 声明 MySQL 数据存储目录为卷
VOLUME /var/lib/mysql
# 在这个例子中,/var/lib/mysql 目录被声明为卷。当容器运行时,Docker 会自动创建一个卷,并将该目录挂载到这个卷上。即使容器停止或删除,MySQL 的数据仍然保留在 Docker 主机的卷中。
示例 2:共享日志目录
假设你有一个应用服务器,你希望将日志目录声明为卷,以便多个服务器实例可以共享日志。
FROM nginx:latest
# 声明日志目录为卷
VOLUME /var/log/nginx
# 在这个例子中,/var/log/nginx 目录被声明为卷。多个 Nginx 容器实例可以共享同一个卷,从而共享日志数据。
注意事项
VOLUME 指令在镜像构建时不会创建实际的卷,而是在容器运行时创建。
如果你在 Dockerfile 中使用 VOLUME 指令,那么该目录的内容将被 Docker 管理的卷覆盖。这意味着,如果你在 Dockerfile 的其他地方(如 COPY 或 ADD)向该目录复制文件,这些文件在容器运行时将被卷的内容覆盖。
VOLUME 指令只能用于声明卷挂载点,不能用于指定卷的名称或类型。卷的名称和类型可以在 docker run 命令中通过 --volume 或 -v 选项指定。
2.8 CMD:容器启动时运行的命令(不常用)
# 语法
CMD ["executable","param1","param2"]
CMD command param1 param2
# 示例
CMD echo $LANG
2.9 ENTRYPOINT:容器启动时运行的命令(常用)
# 语法
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
# 示例
ENTRYPOINT java -jar /data/config.jar
ENTRYPOINT和CMD的异同点:
相同点:
(1)只能写一条,如果写了多条,那么只有最后一条生效
(2)容器启动时才运行,运行时机相同
不同点:
(1)ENTRYPOINT不会被运行的command覆盖,而CMD则会被覆盖
(2)如果我们在Dockerfile种同时写了ENTRYPOINT和CMD,并且CMD指令不是一个完整的可执行命令,那么CMD指定的内容将会作为ENTRYPOINT的参数
2.10 EXPOSE:暴露容器运行时的监听端口给外部
# 格式:
EXPOSE <port> [<port>...]
# 示例:
EXPOSE 8080
EXPOSE 443/tcp 80/tcp
注意:
EXPOSE 并不会让容器的端口访问到主机。在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口
如果没有暴露端口,后期也可以通过docker run -p 8080:8080 方式映射端口,但不能通过 -P 形式映射
2.11 WORKDIR:工作目录
功能类似cd命令。后续指令将在该目录中执行。若WORKDIR指定的目录不存在,则Docker会自动创建。
2.12 其他指令
MAINTAINER:指定作者
USER:设置启动容器的用户
ONBUILD:用于设置镜像触发器
HEALTHCHECK:用于检查容器健康状况
3 制作镜像实例(重点)
3.1 制作镜像语法
docker build -t 镜像名:标签名 Dockerfile路径(可以是绝对路径,也可以是相对路径)
3.2 实例1:构建nginx镜像
(1)编写Dockerfile
[root@harbor ~]# mkdir -p /data/dockerfile/nginx/
[root@harbor ~]# cd /data/dockerfile/nginx/
[root@harbor nginx]# vim Dockerfile
# 使用官方Nginx镜像
FROM nginx
# 复制自定义配置文件
COPY default.conf /etc/nginx/conf.d
# 复制网站文件
COPY html /usr/share/nginx/html
# 暴露Nginx端口
EXPOSE 80
(2)编写自动配置文件和网站文件
这里根据你自己情况来编写default.conf,放在Dockerfile同级目录下
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
将自己的网页存放到html目录中。
mkdir html
echo "hello docker" > html/index.html
(3) 创建镜像
docker build -t mynginx .
(4)运行容器
docker run -d -p 80:80 --name mynginx mynginx
3.3 实例2:构建 Tomcat 应用镜像
vim Dockerfile
# 使用官方的 Ubuntu 镜像作为基础镜像
FROM ubuntu
# 设置环境变量,避免交互式安装问题
ENV DEBIAN_FRONTEND=noninteractive
# 更新包列表并安装必要的软件包
RUN apt-get update && \
apt-get install -y \
curl \
gnupg \
wget \
unzip \
openjdk-8-jdk
# 设置 JAVA_HOME 环境变量
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
ENV PATH=$PATH:$JAVA_HOME/bin
# 下载并安装 Tomcat
ARG TOMCAT_VERSION=9.0.87
ENV TOMCAT_VERSION=$TOMCAT_VERSION
# 使用官方安装包,由于网络原因不使用
# RUN wget https://downloads.apache.org/tomcat/tomcat-9/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz -O /tmp/tomcat.tar.gz && \
# tar -xvf /tmp/tomcat.tar.gz -C /opt/ && \
# ln -s /opt/apache-tomcat-${TOMCAT_VERSION} /opt/tomcat && \
# rm /tmp/tomcat.tar.gz
# 使用局域网安装包
RUN wget http://192.168.3.200/Software/apache-tomcat-${TOMCAT_VERSION}.tar.gz -O /tmp/tomcat.tar.gz && \
tar -xvf /tmp/tomcat.tar.gz -C /opt/ && \
ln -s /opt/apache-tomcat-${TOMCAT_VERSION} /opt/tomcat && \
rm /tmp/tomcat.tar.gz
# 设置 CATALINA_HOME 环境变量
ENV CATALINA_HOME=/opt/tomcat
ENV PATH=$PATH:$CATALINA_HOME/bin
# 暴露 Tomcat 默认的 HTTP 端口
EXPOSE 8080
# 设置工作目录
WORKDIR /opt/tomcat
# 启动 Tomcat 服务
CMD ["catalina.sh", "run"]
# 构建镜像:
docker build -t my-tomcat-image .
# 运行容器:
docker run -d -p 8080:8080 --name tomcat-container my-tomcat-image
4 Dockerfile优化技巧
在编写高效的Dockerfile时,可以遵循以下最佳实践,以便优化镜像的构建和运行性能:
4.1 使用轻量基础镜像
选择小巧的基础镜像,如alpine,以减少最终镜像的体积。
常见的基础镜像
Ubuntu
描述:Ubuntu 是一个基于 Debian 的开源操作系统,广泛用于云和服务器环境中。
优点:广泛的社区支持,丰富的包库,频繁的更新。
使用场景:适用于需要稳定、多功能和易于使用的 Linux 环境的应用。
Alpine Linux
描述:Alpine Linux 是一个面向安全的轻量级 Linux 发行版,使用 musl libc 和 busybox。
优点:非常小的镜像大小(通常在 5MB 左右),提高了安全性和资源效率。
使用场景:非常适合创建小型、安全的容器化应用。
CentOS
描述:CentOS 是一个基于 Red Hat Enterprise Linux 的开源操作系统,具有企业级的稳定性和安全性。
优点:长期支持,企业级部署的好选择。
使用场景:适用于需要企业级支持和广泛兼容性的应用。
Debian
描述:Debian 是一个极其稳定的操作系统,它是 Ubuntu 和许多其他 Linux 发行版的基础。
优点:稳定性强,支持周期长,包管理系统成熟。
使用场景:适合需要长期稳定支持的服务器和应用。
Scratch
描述:在 Docker 的上下文中,scratch 是一个空白的镜像,不包含任何文件或内容。
优点:可以从零开始构建镜像,完全控制镜像内容。
使用场景:适合需要极致轻量化的专用容器,或者构建基于 C/C++ 等不依赖于操作系统发行版的应用。
BusyBox
描述:BusyBox 结合了多个 UNIX 实用工具的单个小型可执行文件,经常用在嵌入式环境中。
优点:非常小巧,适合嵌入式系统和资源受限环境。
使用场景:适用于需要最小化 Linux 系统的简单容器。
4.2 合并命令
每个指令(如 RUN、COPY 等)都会生成一个新的镜像层。使用&&将多个RUN命令合并,减少镜像层数,从而提高构建速度和效率。
如下面例子
RUN wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz \
&& tar -zxvf nginx-$NGINX_VERSION.tar.gz \
&& cd nginx-$NGINX_VERSION \
&& ./configure --prefix=/usr/local/nginx --with-http_ssl_module \
&& make \
&& make install \
4.3 缓存优化:
合理地安排指令顺序。将不常变化的指令放在Dockerfile前面,常变化的放在后面,以利用缓存,加快构建速度。
4.4 使用.dockerignore:
排除不必要的文件,减小上下文大小,提升构建速度。
以下是一个 .dockerignore 文件的示例:
# 忽略所有的 .log 文件
*.log
# 忽略 node_modules 目录
node_modules/
# 忽略 .git 目录
.git/
# 忽略所有的 .env 文件
.env
# 忽略特定目录下的所有文件
src/temp/*
# 忽略所有隐藏文件
.*
# 忽略 build 目录下的所有 .txt 文件
build/*.txt
# 忽略特定目录下的特定文件
config/secret.yml
4.5 多阶段构建
利用多阶段构建,仅将最终运行所需的文件复制到最终镜像,降低镜像大小。
多阶段构建(multi-stage builds)是 Docker 的一个功能,用于简化 Docker 镜像的构建过程并减小镜像体积。
它允许你在一个 Dockerfile 中定义多个构建阶段,只将最终的产物复制到最后的镜像中,从而避免不必要的文件和依赖被包含在最终镜像中。
下面是一个简单的多阶段构建示例,演示如何构建一个 Go 应用程序。
4.5.1. 示例项目结构
.
├── Dockerfile
├── main.go
main.go
文件内容:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
4.5.2. Dockerfile内容
Dockerfile内容如下:
# 第一阶段:构建应用程序
FROM golang:1.18 AS builder
# 设置工作目录
WORKDIR /app
# 复制项目文件
COPY . .
# 编译应用程序
RUN go build -o myapp
# 第二阶段:创建最终镜像
FROM alpine:latest
# 设置工作目录
WORKDIR /app
# 从第一阶段复制可执行文件
COPY --from=builder /app/myapp .
# 运行应用程序
CMD ["./myapp"]
4.5.3. 解释说明
1. 第一阶段:构建应用程序
- 使用
golang:1.18
镜像作为基础镜像。 - 设置工作目录为
/app
。 - 复制项目文件到容器内。
- 运行
go build
命令编译 Go 应用程序,并生成可执行文件myapp
。
2. 第二阶段:创建最终镜像
- 使用
alpine:latest
作为基础镜像。Alpine
是一个体积很小的 Linux 发行版,非常适合用于构建最终的容器镜像。 - 设置工作目录为
/app
。 - 从第一阶段的镜像中复制
myapp
可执行文件到当前阶段的工作目录。 - 设置容器启动时执行的命令为
./myapp
。
最终的镜像基于alpine镜像,只包含myapp
可执行文件,而不包含 Go 的编译器或任何不必要的依赖,体积较小。
4.5.4. 构建和运行镜像
1. 构建 Docker 镜像
docker build -t myapp:latest .
2. 运行 Docker 容器
docker run --rm myapp:latest
输出应为:
Hello, World!
4.5.5. 多阶段构建优点
- 减小镜像体积:通过只将必要的文件复制到最终的镜像中,减小了镜像的体积。
- 提高安全性:减少了最终镜像中的不必要的工具和依赖,降低了攻击面。
- 简化构建过程:在一个 Dockerfile 中管理所有的构建阶段,简化了构建和维护过程。
多阶段构建非常适合用于编译型语言(如 Go、Java、C++)的项目,也可以用于前端项目的构建(如构建 React 或 Angular 应用并将其部署到 Nginx 容器中)。
4.6 优化入口点和命令
使用ENTRYPOINT和CMD正确配置容器启动方式,确保灵活性和一致性。
4.7 按需复制文件
只复制构建所需的文件,而不是整个项目