单页面应用
使用vite 快速创建一个 vue 项目
pnpm create viteDockerfile
FROM nginx:1.15.2-alpine
COPY /docs/.vitepress/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
ENTRYPOINT ["nginx","-g","daemon off;"]- 把打包出来的
/docs/.vitepress/dist文件复制到容器内的/usr/share/nginx/html - 把 nginx 的配置文件
nginx.conf复制到容器内的/etc/nginx/nginx.conf
将命令通过以下几步翻译为一个 Dockerfile:
- 选择一个基础镜像。由于需要在容器中执行构建操作,我们需要 node 的运行环境,因此
FROM选择 node。 - 将以上几个脚本命令放在
RUN指令中。 - 启动服务命令放在
CMD指令中。
FROM node:14-alpine
WORKDIR /code
ADD . /code
RUN yarn && npm run build
CMD npx serve -s build
EXPOSE 3000构建完成。然而还可以针对以下两点进行优化。
- 构建镜像时间过长,优化构建时间。
- 构建镜像文件过大,优化镜像体积。
构建时间优化:构建缓存
我们注意到,一个前端项目的耗时时间主要集中在两个命令:
- pnpm i
- pnpm run build
在本地环境中,如果没有新的 npm package 需要下载,不需要重新 npm i。
那 Docker 中是不也可以做到这一点?
在 Dockerfile 中,对于 ADD 指令(官方文档)来讲,如果添加文件内容的 checksum 没有发生变化,则可以利用构建缓存(Best practices for writing Dockerfiles)。
而对于前端项目而言,如果 package.json/pnpm-lock.yaml 文件内容没有变更,则无需再次 pnpm i。
将 package.json/pnpm-lock.yaml 事先置于镜像中,安装依赖将可以获得缓存的优化,优化如下。
FROM node:16-alpine as builder
WORKDIR /app
# 单独分离 package.json,是为了安装依赖可最大限度利用缓存
ADD package.json pnpm-lock.yaml* /app/
# 此时,yarn 可以利用缓存,如果 yarn.lock 内容没有变化,则不会重新依赖安装
RUN yarn global add pnpm && pnpm i --frozen-lockfile;
ADD . /app
RUN pnpm run build
CMD npx serve -s build
EXPOSE 3000进行构建时,若可利用缓存,可看到 CACHED 标记。
$ docker-compose up --build
...
=> CACHED [builder 2/6] WORKDIR /code 0.0s
=> CACHED [builder 3/6] ADD package.json yarn.lock /code/ 0.0s
=> CACHED [builder 4/6] RUN yarn 0.0s
...构建体积优化:多阶段构建
我们的目标是提供静态服务(资源),完全不需要依赖于 node.js 环境进行服务化。node.js 环境在完成构建后即完成了它的使命,它的继续存在会造成极大的资源浪费。
我们可以使用多阶段构建进行优化,最终使用 nginx 进行服务化。
- 第一阶段 Node 镜像:使用 node 镜像对单页应用进行构建,生成静态资源。
- 第二阶段 Nginx 镜像:使用 nginx 镜像对单页应用的静态资源进行服务化。
FROM node:16-alpine as builder
WORKDIR /app
# 单独分离 package.json,是为了安装依赖可最大限度利用缓存
ADD package.json pnpm-lock.yaml* /app/
# 此时,yarn 可以利用缓存,如果 yarn.lock 内容没有变化,则不会重新依赖安装
RUN yarn global add pnpm && pnpm i --frozen-lockfile;
ADD . /app
RUN pnpm run build
# 选择更小体积的基础镜像
FROM nginx:alpine
COPY --from=builder app/dist /usr/share/nginx/html启动容器
我们将 Dockerfile 命名为 simple.Dockerfile
该 docker compose 配置位于 cra-deploy/simple.Dockerfile
version: "3"
services:
simple:
build:
context: .
dockerfile: simple.Dockerfile
ports:
- 4000:80使用 docker-compose up --build simple 启动容器。
创建镜像
$ docker build --rm -f Dockerfile -t docker-react:latest .
[+] Building 0.4s (8/8) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 190B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/nginx:1.15.2-alpin 0.0s
=> [1/3] FROM docker.io/library/nginx:1.15.2-alpine 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 2.33kB 0.0s
=> [2/3] COPY ./index.html /var/www 0.1s
=> [3/3] COPY /docker/nginx/nginx.conf /etc/nginx/nginx.conf 0.1s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:fab2f97f0c3f4adcf994cd7be5f9f8b566a584582 0.0s
=> => naming to docker.io/library/web创建容器
docker run --rm -d -p 80:80 docker-react:latest目录结构如下
├── Dockerfile
└── docker
└── nginx
└── nginx.conf配置 nginx
创建 nginx 配置文件 nginx.conf
# auto detects a good number of processes to run
worker_processes auto;
#Provides the configuration file context in which the directives that affect connection processing are specified.
events {
# Sets the maximum number of simultaneous connections that can be opened by a worker process.
worker_connections 8000;
# Tells the worker to accept multiple connections at a time
multi_accept on;
}
http {
# what times to include
include /etc/nginx/mime.types;
# what is the default one
default_type application/octet-stream;
# Sets the path, format, and configuration for a buffered log write
log_format compression '$remote_addr - $remote_user [$time_local] '
'"$request" $status $upstream_addr '
'"$http_referer" "$http_user_agent"';
server {
# listen on port 80
listen 80;
# save logs here
access_log /var/log/nginx/access.log compression;
# where the root here
root /var/www;
# what file to server as index
index index.html index.htm;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to redirecting to index.html
try_files $uri $uri/ /index.html;
}
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
add_header Cache-Control "public";
}
# JavaScript and CSS files
location ~* \.(?:css|js)$ {
try_files $uri =404;
expires 1y;
access_log off;
add_header Cache-Control "public";
}
# Any route containing a file extension (e.g. /devicesfile.js)
location ~ ^.+\..+$ {
try_files $uri =404;
}
}
}nginx 镜像
docker run -it --rm -p 3000:80 nginx:alpine默认配置文件位于 /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/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 /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}默认配置文件位于 /etc/nginx/conf.d/default.conf
server {
listen 80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/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 /usr/share/nginx/html;
}
}