作为一名前端开发者,你可能会说,Docker和我有啥关系,我又用不到,因为它看起来更像是后端或者DevOps的领域。但实际上,Docker对前端开发同样有很多好处,比如:
- Docker可以帮助你在本地环境中快速搭建和模拟生产环境。你知道,有时候在本地开发环境中一切正常,但代码一到生产环境就出问题了。使用Docker,你可以创建一个与生产环境尽可能接近的容器,这样就可以减少“
在我机器上可是好的
”这类问题。 - 简化团队协作。想象一下,新同事加入项目,他们需要配置本地环境。传统方式可能需要安装各种依赖、设置数据库等等,这既费时又可能出错。但有了Docker,他们只需要拉取一个镜像,运行一个容器,所有环境就配置好了,可以立即开始工作,
这极大的降低了新同事介入工作的成本
。所以用上了Docker,你再也不用写一篇文档来告知如何配置本地开发环境,巴拉巴拉一大堆。 - 前端项目通常会依赖后端API或数据库等服务,甚至有时候还需要配置代理来解决本地开发跨域的问题,这些真的很头疼。使用Docker Compose,你可以定义一个多容器的应用,其中包括前端应用、API服务器、数据库等,一键启动整个应用栈。
简单的理解Docker的原理
Docker是基于Linux容器(LXC)技术,但提供了更高层次的抽象和更简单的工具链。Docker使用容器来运行应用,容器是一种轻量级的、可执行的软件包,其中包含了运行某个软件所需的代码、运行时、系统工具、库和设置。这种实现与虚拟机不同,它们不需要包含操作系统的完整副本,而是与宿主机共享内核,只包含应用程序及其依赖,因此它们更加轻量级和快速
。Docker使用镜像来创建容器,镜像是一个轻量级、可移植、自给自足的软件运行环境的模板。你可以把它想象成一个快照,任何时候基于这个镜像启动的容器,都会是一个一模一样的环境。
Docker的工作流程通常包括构建、运输和运行。你首先在本地构建一个Docker镜像,然后可以将它推送到Docker Hub或其他注册中心,最后在任何安装了Docker的机器上运行这个镜像,就可以启动一个一致的容器环境。
就此打住,废话不宜过多,但是考虑到前端研发同学可能有些并没接触过docker,还是有点点必要科普一下。
基于Docker开发前端应用
学以致用,假社我们就是奔着统一环境的目的来的,解决新同学加入团队需要配置一堆本地开发环境的痛点,此时我们采取使用Docker的开发方式是如何的呢?
再次假设,如我们需要使用react来开发前端应用,此时,我们的第一步,依然是创建一个 react应用,npx create-react-app my-app-docker
完了之后,随后就有一些不同了,我们要多追加一个Docker描述文件Dockerfile,其内容如下,然后竟就是打包镜像,启动容器,剩下的开发体验和本地启动服务完全一样。
# 使用官方Node.js作为基础镜像
FROM node:latest
# 设置工作目录
WORKDIR /app
# 复制package.json和package-lock.json(如果有)
COPY package*.json ./
# 安装项目依赖
RUN npm install
# 复制项目文件到工作目录
COPY . .
# 暴露容器的端口号
EXPOSE 3000
# 运行前端服务
CMD ["npm", "start"]
随后,我们执行 docker build -t my-app-docker .
来打一个镜像出来。这个命令的解析是:
docker build
: Docker CLI命令,用于构建Docker镜像。t my-frontend-app
:t
标志指定要创建的镜像的名称(在这里是my-frontend-app
)。这个名称可以在后续的docker run
命令中使用。.
: 指定Dockerfile所在的路径。在这个例子中,.
表示当前目录。
执行的过程如下所示。
打镜像完毕之后,我们可以看到Docker的管理段中,Images里面会多一个镜像,就是我们刚才打的镜像。
此时,有了这个镜像,我们就可以启动这个镜像了,你可以直接使用界面的方式启动,就是点击那个play按钮,出来后:
这里面有一些配置参数,映射端口号
,容器名字
,映射目录
,环境变量
等。但是我更加推荐你使用命令的方式来,表示用习惯了会更加快。
启动镜像
docker run -p 3000:3000 my-frontend-app
,这个命令的含义如下:
docker run
: 运行一个Docker容器的命令。p 3000:3000
:p
标志将容器内部的端口映射到宿主机的端口。3000:3000
的意思是将容器的3000端口映射到宿主机的3000端口。my-frontend-app
: 这是你之前构建的镜像的名称。
好了之后,你应该可以在 http://localhost:3000 看到你的react应用了。
如何实现文件同步呢?
我们不可能在开发的过程中变更一样代码,就打一个镜像,这样做效率也太低了,有什么办法吧本地变更的文件同步到容器中呢?答案就是我们使用界面方式启动时,里面看到的那个 Volumes。命令来执行就是:
docker run -p 3000:3000 -v $(pwd):/app my-frontend-app
,
• -v $(pwd):/app
: -v
标志用于挂载卷。$(pwd)
是当前工作目录的路径,/app
是容器内的路径。这意味着你的工作目录将被挂载到容器的/app
目录,从而实现文件同步。
我们试着改变一下本地的文件,从命令行里面可以看到,容器的环境中同步到了变化,开始编译了。
这就意味着,你的本地的变更,将会同步反应到容器中,实现本地代码变更,热更新到界面上,和本地开发无任何不同。
镜像太大,有没有办法变小?
我们可以使用比较小的基础镜像,以改用node:alpine
,因为Alpine Linux版本的镜像通常更小。
可以看到,我们的镜像直接就小了一半。从1.64G压缩到了 735M。有人会讲了,这依然很大啊,还有办法更小吗?
所以,除此之外,还有更加进一步的优化办法吗?当然,方法还不仅仅如此,比如,我们还可以尝试多阶段构建
,因为,react最终的产物就是一堆html css js。所以,我们这么玩,分两个阶段。
# 第一阶段:构建
FROM node:alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 第二阶段:运行
FROM nginx:alpine
COPY --from=build-stage /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
但是,我们发现,这个似乎只建议在生成环境打镜像的时候用,开发的时候,并不合适,因为我们要做目录映射,要做热更新。
更上一层楼
如果,我们需要依赖某几个后端服务,怎么办呢?答案是,我们可以使用**Docker Compose,**这个compose的意思就可以简单的理解为组合。比如一个简单的:
代码语言:javascript复制version: '3' # 指定了使用的Compose文件版本
services: # 定义了多个服务(容器),可以相互协作
frontend: # 定义了名为“frontend”的服务
build: . # 指定Dockerfile所在的目录(当前目录),用于构建镜像
ports:
- "3000:3000" # 将容器的3000端口映射到宿主机的3000端口
volumes:
- .:/app # 将当前目录挂载到容器的/app目录,实现代码同步
depends_on:
- backend # 表示“frontend”服务依赖于“backend”服务
backend: # 定义了名为“backend”的服务
image: "my-go-service"
ports:
- "5000:5000" # 将容器的5000端口映射到宿主机的5000端口,这样你就可以通过宿主机的端口访问后端服务。
这个docker-compose.yml
文件定义了两个服务:一个前端服务和一个后端服务。前端服务会构建一个Docker镜像(基于当前目录下的Dockerfile),注意,因为我们frontend这个服务里面有build字段,所以才会构建,并且将宿主机的当前目录挂载到容器内部的/app
目录,以便实现实时同步。后端服务则直接使用一个已经存在的镜像,他不需要构建。一个docker-compose.yml
可以只方一个服务都是OK的。
docker-compose up
命令到底做了些啥
简单的讲,当你在包含docker-compose.yml
文件的目录中运行docker-compose up
命令时,Docker Compose会执行以下操作:
- 读取配置:Docker Compose首先读取
docker-compose.yml
文件,解析里面定义的服务、网络、卷等配置。 - 构建镜像:对于那些需要构建的服务(如我们的例子里面,
frontend
),Docker Compose会根据Dockerfile构建镜像。构建的镜像会被存储在本地的Docker镜像库中。 - 拉取镜像:对于直接指定了镜像名称的服务(如
backend
),如果本地没有这个镜像,Docker Compose会从Docker Hub或其他指定的镜像仓库拉取镜像,本地有当然就直接用本地的了。 - 创建网络:Docker Compose会创建一个默认的网络,使得定义在
docker-compose.yml
文件中的服务可以互相通信。 - 启动容器:Docker Compose会根据配置启动服务对应的容器。如果有
depends_on
配置,Docker Compose会先启动依赖的服务。 - 应用卷映射:对于定义了卷映射的服务,Docker Compose会将指定的宿主机目录或文件挂载到容器内的相应位置。
- 端口映射:Docker Compose会将容器的端口映射到宿主机的端口,使得可以从宿主机访问容器内部的应用。
- 日志输出:默认情况下,Docker Compose会捕获并输出所有容器的stdout和stderr到终端,让你可以实时看到输出。
- 运行状态:除非你在命令后添加了
d
参数来让服务在后台运行,否则Docker Compose会保持在前台,并且当你按下Ctrl C
时停止所有服务。
通过这个过程,Docker Compose简化了多容器Docker应用的管理,你不需要手动执行一系列的docker build
和docker run
命令来启动你的应用。所有的配置都可以在docker-compose.yml
文件中声明,使得整个过程更加简洁。
因此,我们看到,实际上在前端开发上,也可以使用docker,docker这个技术针对的不仅仅是后端各种服务,他更加像是一种思想,万物皆容器。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!