在没有云服务提供商的情况下部署 MEAN Stack 应用

发布: (2026年3月1日 GMT+8 15:22)
7 分钟阅读
原文: Dev.to

Source: Dev.to

🏗️ 架构

我们采用 monorepo(单仓库)方式,这意味着 Angular 前端和 Node.js 后端都位于同一个仓库中。其工作流程如下:

  1. 将代码推送production 分支。
  2. GitHub Actions 构建 Docker 镜像。
    • 镜像被推送到 Docker Hub
    • 在你的 VM 上的 Self‑Hosted Runner 拉取最新镜像并重启容器。
  3. Nginx 充当反向代理,负责路由流量。

如果你想了解这与云特定托管方式的区别,请查看我之前的文章《Hosting a Node.js Server in an EC2 Instance》。

1. 设置服务器(VirtualBox)

我在此使用了 Debian 虚拟机

  • 网络: 将虚拟机的适配器设置为 桥接模式。这使得虚拟机可以从路由器获取 IP,成为局域网(LAN)中的真实节点。
  • 访问: 你应该能够通过 SSH 登录:ssh user@your_vm_ip.

如需详细了解如何处理 LAN 网络和端口转发,以便让你的服务器可以从互联网访问,请参阅我的文章:How Web Technology Works – Part 01.

2. Docker Hub 与 GitHub Secrets

要自动推送镜像,GitHub 需要获得与 Docker Hub 通信的权限。不要使用你的账户密码。

  1. 前往 Docker Hub > Settings > Personal access tokens

  2. 创建一个 New Access Token,并授予 Read & Write 权限。

  3. 在你的 GitHub 仓库中,进入 Settings > Secrets and variables > Actions

  4. 添加以下 Secrets:

    • DOCKERHUB_USERNAME – 你的 Docker Hub 用户名
    • DOCKERHUB_TOKEN – 刚才创建的令牌

3. 自托管运行器

与其使用 GitHub 的服务器进行部署,我们改为使用自己的 VM。这称为 自托管运行器

  1. 在 GitHub:Settings > Actions > Runners > New self‑hosted runner
  2. 选择 Linux 并按照指令在你的 VM 上下载并配置它。

配置完成后,将其安装为服务,使其在后台运行:

sudo ./svc.sh install
sudo ./svc.sh start

4. 容器化(代码)

由于我们使用的是 monorepo,需要分别编写 Dockerfile 并使用一个 Compose 文件。

后端 Dockerfile (backend/Dockerfile)

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]

前端 Dockerfile (frontend/Dockerfile)

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist/your-app-name /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Docker Compose (docker-compose.yml)

version: '3.8'
services:
  backend:
    image: your-docker-username/mean-backend:latest
    extra_hosts:
      - "host.docker.internal:host-gateway"
    container_name: mean-backend
    restart: always
    ports:
      - "8080:8080"

  frontend:
    image: your-docker-username/mean-frontend:latest
    container_name: mean-frontend
    restart: always
    depends_on:
      - backend
    ports:
      - "81:80"

5. Nginx 反向代理

在宿主虚拟机上安装 Nginx:

sudo apt install nginx

我们使用它将 80 端口的流量路由到容器。

配置 (/etc/nginx/sites-available/default):

server {
    listen 80;
    server_name 10.131.44.201; # Use your VM IP

    location /api/ {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location / {
        proxy_pass http://localhost:81;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

检查配置语法:

sudo nginx -t

重新加载 Nginx:

sudo systemctl reload nginx

6. CI/CD 流水线

创建 .github/workflows/deploy.yml。此脚本会自动化整个过程。

name: Build and Deploy
on:
  push:
    branches: [ production ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: Build and Push Backend
        run: |
          docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/mean-backend:latest ./backend
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/mean-backend:latest
      - name: Build and Push Frontend
        run: |
          docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/mean-frontend:latest ./frontend
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/mean-frontend:latest
      - name: Deploy on Self‑Hosted Runner
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.VM_HOST }}
          username: ${{ secrets.VM_USER }}
          key: ${{ secrets.VM_SSH_KEY }}
          script: |
            cd /path/to/your/repo
            docker compose pull
            docker compose up -d

Note: 调整 hostusernamekeycd 路径,以匹配你的环境。

🎉 你完成了!

现在你已经拥有一个完整自动化的 CI/CD 流水线,能够构建 Docker 镜像、将其推送到 Docker Hub,并使用自托管的 GitHub Actions Runner 和 Nginx 作为反向代理,在本地 VM 上进行部署。无需任何云服务提供商。祝编码愉快!

本地虚拟机上的 CI/CD 流水线

以下是完整的 GitHub Actions 工作流以及验证部署的步骤。

GitHub Actions 工作流 (.github/workflows/ci-cd.yml)

name: CI/CD

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: self-hosted
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Build Backend Image
        run: |
          cd ./backend
          docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/mean-backend .

      - name: Build Frontend Image
        run: |
          cd ./frontend
          docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/mean-frontend .

      - name: Push Backend Image
        run: |
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/mean-backend

      - name: Push Frontend Image
        run: |
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/mean-frontend

  deploy:
    needs: build
    runs-on: self-hosted
    steps:
      - name: Pull and Restart Services
        run: |
          cd ~/your-app-dir
          docker-compose pull
          docker-compose up -d

验证流水线

1. CI/CD 成功

将代码推送到 main 后,您应该在 GitHub Actions 中看到全部绿色勾选。

GitHub Actions Success

2. Docker Hub

您的镜像将在 Docker Hub 上显示 latest 标签。

Docker Hub Images

3. 运行中的服务

登录到虚拟机,确认容器已启动并运行。

Docker Containers Running

您的应用现在应该可以通过本地网络访问:

http://VM_IP/

为什么这样有效

本地虚拟机 上设置 CI/CD 流水线表明 DevOps 本质上是关于 逻辑和架构,而不仅仅是您选择的云提供商。通过使用 桥接模式的 VirtualBox,您可以获得类似生产环境的环境,完全控制网络和部署周期——且无需任何云费用。

关键要点

  • 基础设施灵活性 – 相同的设置可在任何 Linux 机器上运行:虚拟机、Raspberry Pi 或裸金属服务器。
  • 自动化 – 自托管 runner 让您在本地保留部署逻辑,同时仍利用 GitHub 进行构建。
  • 单仓库效率 – 在同一个仓库中管理 Angular 前端和 Node.js 后端,简化 CI/CD 工作流。

在设置本地环境时遇到了哪些挑战? 在评论中告诉我吧!

0 浏览
Back to Blog

相关文章

阅读更多 »

当工作成为心理健康风险时

markdown !Ravi Mishrahttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fu...