按照帖子:Deploy MERN Stack on AWS EC2 with Docker via GitHub Actions部署我的新项目及过程中遇到的问题
Deploy MERN Stack on AWS EC2 with Docker via GitHub Actions
如何部署
将 MERN 项目的前端后端分别放在 github 两个仓库中,使用 github action,我们将分别为这两个存储库构建 Docker 镜像。这些镜像将被推送到 DockerHub 并保存在 DockerHub 上的两个不同的存储库中。在 AWS EC2 上,我们分别为前端后端配置 self-host runner。当我们的镜像推送到 dockerhub 后,runner 会自动拉取镜像并在 EC2 上运行。

Step1: 在 DockerHub 创建 public Respository
登录到 dockerhub,如果是第一次注册需要记下账号和密码,这很重要;创建两个公共存储库,一个用于前端,另一个用于后端。

当我们在 GitHub 上为项目创建存储库时,我们必须将 DockerHub 用户名和密码存储在 GitHub Secrets 中。
Step 2: 允许访问 MongoDB 数据库
因为我的 MERN 项目使用的是 MongoDB Atlas,这允许我在线访问数据库,因此我需要配置从 Internet 上的任何位置访问 MongoDB。登录后,左侧导航栏 -> Security -> Network Access

现在,您将看到数据库的 IP 访问列表。如果您看到 IP“0.0.0.0/0”,则可以从任何地方访问它。如果您看到任何其他 IP 地址,请删除该地址并输入给定的 IP 地址。

Step 3: 推送代码到 Github 仓库并配置 GitHub Secrets
MERN 项目中应该有两个文件夹,一个前端,一个后端,分别连接创建好的 Github 仓库

为前端和后端创建两个不同的存储库,并将前端文件夹文件推送到前端 GitHub 存储库,将后端文件夹文件推送到项目的后端 GitHub 存储库。
新建两个仓库的GitHub Secrets:Setting -> 左侧导航栏 -> Security -> secrets and variables -> Action -> New repository secrets

GitHub Secrets 作用:在代码中对我们的用户名、密码和 API 进行硬编码不是好的做法。为了在生产环境中保护我们的凭证,GitHub 提供了添加密钥的功能。这些密钥可以是我们在开发时添加的“env”变量、连接密钥或我们的凭证。
⚠️:这里两个仓库都需要添加 DockerHub 的用户名及密码,用于后面 Github Action push 和 pull 镜像。
Step 4: 创建 EC2 实例
因为我之前已经申请过 EC2,所以这一步不做赘述,第一次申请详见原贴 Step 4: Deploy MERN Stack on AWS EC2 with Docker via GitHub Actions。
这里需要额外补充的是:
如果想要在本地登录到 EC2,需要新建 key-pair,点击创建会自动下载到电脑,复制保存到~/.ssh/文件夹
打开本地 terminal,输入
ssh -i ~/.ssh/kp-mac.pem ubuntu@your ipaddress其中 kp-mac.pem 是下载的 key-pairEC2 同一个实例可以跑很多不同的项目,只需要新建镜像,开不同的端口,最后用 Nginx 指定端口即可,这样可以节省很多额度。
新建端口:选中已有的 Instance -> Security -> Security groups -> Inbound rules -> Edit Inbound rules -> Add rules

Step 5: 在 EC2 实例上安装 Docker
登录到 EC2,两种登录方式,一种是在线登录,一种本地登录
在线登录
选中 Instance -> Connect

选择“EC2 Instance Connect”,选择 ip,然后单击 Connect。

现在我们将看到一个 AWS 终端。
本地登录
本地终端输入:ssh -i ~/.ssh/kp-mac.pem ubuntu@your_ip_address,也会进入到 AWS 终端
安装 Docker
先验证 Docker 是否安装,附图是没有安装的结果
1 | # 验证docker是否安装 |

如果没有安装,则按照下面命令安装
1 | sudo apt-get update |
此时命令 docker ps 报错:permission denied,是因为我们没给他授权

授权后 docker ps 命令就不再报错了
1 | # 如果上一行命令docker ps报错:permission denied,我们要给他授权 |

现在,我们成功在 EC2 上安装 docker 并授权
Step 6: 在 EC2 上创建 Self-Hosted Runners
‼️ 转到 Github 中,针对前端后端仓库分别创建 runner
github -> bookhub_backend -> Settings -> Actions -> Runners -> New-self-hosted runner

我们的 EC2 实例是 Ubuntu,因此请为运行器映像选择“Linux”选项。然后你将看到用于创建运行程序的命令;逐个复制这些命令并在我们的 EC2 终端上运行。
需要注意的点:
- 创建文件夹时需要sudo
- add the name of the runner, “aws-ec2.”
- 其他都默认配置
将自托管运行器配置为服务:要在终端中将 Runner 配置为服务,请运行以下命令:
1 | sudo ./svc.sh install |
重复上述步骤,分别为前端后端创建 runner,如果有疑问请详见原贴:Step 6: Deploy MERN Stack on AWS EC2 with Docker via GitHub Actions。

Step 7: 为后端仓库创建 Dockerfile 和 CICD 工作流
我的项目后端是TypeScript,Node.js 本身只能识别JavaScript,直接 node index.ts 会报找不到模块或语法错误。要在容器里“运行”TypeScript,需要先用 tsc 编译,再跑编译结果
部署
在项目根目录里加上 tsconfig.json
1 | { |
修改 package.json:
1 | { |
项目根目录加上 dockerfile
1 | FROM node:20-alpine3.18 |
创建工作流
现在在backend文件夹下创建一个.github文件夹。在.github文件夹中,创建workflow文件夹,并在此文件夹中创建文件cicd.yml。
1 | # cicd.yml |
完成以上配置后,推送代码到 github,github action 会自动构建和部署,成功完成这些步骤后,我们的后端应用程序会部署到 AWS EC2
验证部署
验证 cicd 是否正常,ssh 到 ec2
1 | sudo docker ps # 看所有跑起来的docker进程 |
打开后端链接:http://
当然,一次性部署成功是小概率的,可能遇到各种各样的问题,需要根据报错信息逐步定位,下面是我部署过程中遇到的问题
遇到的问题
本地 docker build 报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16docker build -t ella0110/bookhub_backend .
[+] Building 31.4s (3/3) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 195B 0.0s
=> ERROR [internal] load metadata for docker.io/library/node:alpine3.18 31.1s
=> [auth] library/node:pull token for registry-1.docker.io 0.0s
------
> [internal] load metadata for docker.io/library/node:alpine3.18:
------
Dockerfile:1
--------------------
1 | >>> FROM node:alpine3.18
2 | WORKDIR /src
3 | COPY package.json ./
--------------------
ERROR: failed to solve: DeadlineExceeded: DeadlineExceeded: DeadlineExceeded: node:alpine3.18: failed to resolve source metadata for docker.io/library/node:alpine3.18: failed to authorize: DeadlineExceeded: failed to fetch oauth token: Post "https://auth.docker.io/token": dial tcp 103.97.3.19:443: i/o timeout原因:Docker daemon 在拉取
node:alpine3.18镜像时,连不上 Docker Hub 的 Registry / Auth 服务,网络请求超时解决:ERROR: failed to solve: node:18-alpine 解决办法,先 pull 再 build
github action deploy 报错:
1
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.47/images/create?fromImage=ella0110%2Fbookhub_frontend&tag=latest": dial unix /var/run/docker.sock: connect: permission denied
原因:docker 没有 sudo 权限,赋予权限,然后重启 runner
1
2
3
4
5# 把 ubuntu 加到 docker 组
sudo usermod -aG docker ubuntu
cd /home/ubuntu/actions-runner-backend/
sudo ./svc.sh stop
sudo ./svc.sh start验证 cicd 是否正常,ssh 到 ec2
1
2
3
4
5
6
7
8sudo docker ps # 看所有跑起来的 docker 进程
sudo docker logs bookhub-frontend-container # 看 log
sudo docker images # 列出所有镜像
sudo docker rmi <image> # 删除不需要的镜像
sudo docker ps -a # 列出所有容器
sudo docker rm <container> # 删除不需要的容器看 log 的报错:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17sudo docker logs bookhub-backend-container
> backend@1.0.0 start
> node index.js
node:internal/modules/cjs/loader:1189
throw err;
^
Error: Cannot find module '/src/index.js'
at Module._resolveFilename (node:internal/modules/cjs/loader:1186:15)
at Module._load (node:internal/modules/cjs/loader:1012:27)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:12)
at node:internal/main/run_main_module:30:49 {
code: 'MODULE_NOT_FOUND',
requireStack: []
}这里的 index.js 修改了很多版,包括:把 index.ts 移到根目录下/修改 package.json 为
"start": "node src/index.js"等根本原因是:我的代码是 typescript 写的,但这里跑的是 js 的主入口,我指定的是 src 下的 ts 文件,需要增加 dist 编译步骤
Github Action 多个环境变量注入
使用
--env-file,可以在工作目录里动态生成一个.env,然后让 Docker 一次性加载:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16- name: Generate .env file
run: |
cat <<EOF > .env
MONGO_PASSWORD=${{ secrets.MONGO_PASSWORD }}
DB_USER=${{ secrets.DB_USER }}
DB_PASS=${{ secrets.DB_PASS }}
API_KEY=${{ secrets.API_KEY }}
EOF
- name: Run container with env-file
run: |
sudo docker run -d \
--name bookhub-backend-container \
-p 5555:5555 \
--env-file .env \
ella0110/bookhub_backend:latest.env文件就像本地开发时的那样,变量多了也很整洁。、我的 mern 后端中,index 指定端口为 3001,但是 aws ec2 我开了两个 inbound 端口,分别为前端和后端,后端是 5555,现在我 docker 在 ec2 中正常跑起来了,我想测试后端,但是在浏览器输入
http://<Your-EC2-Public-IP>:5555/api/hotels/显示This page isn’t working<Your-EC2-Public-IP> is currently unable to handle this request. HTTP ERROR 502原因:在 cicd.yml 中,写的是
sudo docker run -d -p 5555:5555 … ella0110/bookhub_backend,同时也没有向里传入 PORT 环境变量,这相当于把宿主机的 5555 转到容器的 5555,可是容器里根本没服务在 5555 上,于是请求转发失败,Nginx/Docker proxy 拿不到后端返回,就返回了 502。解决 1:把宿主的 5555 映射到容器的 3001
1
2
3
4
5sudo docker run -d \
-p 5555:3001 \
--name bookhub-backend-container \
-e MONGO_PASSWORD='你的 Mongo 密码' \
ella0110/bookhub_backend:latest解决 2:传入 PORT=5555
Step 8: 部署前端,写 dockerfile, cicd.yml,步骤同后端
但有一些区别,前端的静态文件在 build 阶段就已经确定了,所以不能将 github secrets 在 docker run 时传入,需要提前导入
部署
dockerfile
1 | FROM node:alpine3.18 as build |
Cicd.yml
1 | name: Deploy bookhub-frontend |
遇到的问题:
github action deploy 报错:permission denied while trying to connect to the Docker daemon socket
原因:docker 没有 sudo 权限,赋予权限,然后重启 runner
1
2
3
4
5
6# 把 ubuntu 加到 docker 组
sudo usermod -aG docker ubuntu
# 重启 runner
cd /home/ubuntu/actions-runner-frontend/
sudo ./svc.sh stop
sudo ./svc.sh start另外,这里给 ubuntu 赋权限是因为,
ps aux | grep runsvc.sh,最左边的就是用户组
前端展示出来了,但是没有图片,后端连接失败,console 中显示报错:
1
2index-CMKHi_ND.js:42
GET http://<Your-EC2-Public-IP>:5173/api/hotels 404 (Not Found),原因:github action 环境变量原来的写法在 docker run 是才导入,但是前端静态文件的编译在 docker build 阶段就已经确定了,所以要把环境变量提前到 docker build 阶段
1
2
3
4
5
6
7
8# dockerfile
# 接收外部参数,并设为环境变量,Vite 会在 build 时替换
ARG VITE_API_BASE_URL
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
COPY . .
RUN npm run build1
2
3# cicd.yml
docker build --build-arg VITE_API_BASE_URL="${{ secrets.VITE_API_BASE_URL }}" --build-arg VITE_STRIPE_PUB_KEY="${{ secrets.VITE_STRIPE_PUB_KEY }}" -t ella0110/bookhub_frontend .跨域请求报错:
1
Access to fetch at 'http://<Your-EC2-Public-IP>:5555/api/hotels' from origin 'http://<Your-EC2-Public-IP>:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
原因:在后端没有配置前端的跨域 url,在后端的 github action 新增变量:FRONTEND_URL 指向前端链接及端口
1
2
3
4
5
6
7app.use(
cors({
// 让我们的服务器只接收这个链接的请求,并且这个 URL 必须携带 credentials,也就是要有 cookies
origin: process.env.FRONTEND_URL,
credentials: true,
})
);
域名解析及 Nginx 反向代理
我有一个域名 trillobe.com,我想把 bookhub.trillobe.com 映射到 http://
:5173/,但我现在已经有一个 api.trillobe.com 映射到 http:// 了,我该怎么办 解决:DNS 只能把域名指向 IP,不能指定端口。增加 A 记录
1
2
3主机记录 类型 记录值 说明
bookhub A <Your-EC2-Public-IP> bookhub.trillobe.com 指向前端服务器
api.bookhub A <Your-EC2-Public-IP> api.bookhub.trillobe.com 指向后端服务器此时 bookhub.trillobe.com 和 api.bookhub.trillobe.com 打开应该显示这个界面(因为之前我的 ec2 已经安装过 nginx 了,如果还没安装需要安装后才会显示下面的界面。另外还没配证书所以还是 http 的,显示 Not Secure)
