【山竹记账后端】4.网站部署上线


大纲链接 §

[toc]


部署上线的时机

  • 项目搭建早期就应该部署一个初始可启动的项目服务上线
    • 大致跑通整个流程,逐步完善流程
    • 方便渐进式地 排查问题 -> 修改代码 -> 实现功能代码
    • 每次只需处理少数的 bug,及时揭露风险

1. 购买便宜的云服务器 ⇧

怎么把代码弄上线

  • 弄出一个生产环境,用来运行最终代码

买服务器

  • 阿里云,市场份额最高,服务响应及时,价格较高 服务器链接
  • 腾讯云,市场份额较低,优惠力度大 服务器链接
  • 云服务商都比较鸡贼,需要预充100元才给你开机,余额不足的需要补充到100元

机型

  • 新手选最便宜的,一年到三年即可
  • 如果还是觉得贵,可以按使用量付费,用完即毁(切记)

购买阿里云 按量付费 部署流程测试 ⇧

付费类型、可用区 ⇧

按量付费,按量付费实例不支持备案服务,仅作为部署流程测试,用完即停机,并且释放实例;之后重新购买稳定服务提供项目展示

  • 付费类型-按量付费;暂时选择最便宜的
  • 可用区:上海 可用区O 仅支持 ECS
    • 同可用区内网免费:在同一个可用区内,云服务器、数据库(RDS)和对象存储(OSS)之间的内网通信是完全免费的
    • 跨可用区流量收费:如果您将应用部署在可用区A,而数据库部署在可用区B(跨可用区高可用架构)
    • 虽然能防灾,但云厂商会对这两个可用区之间传输的内网数据收取同地域跨可用区流量费

按量付费 可用区


实例和镜像 ⇧

  • 实例价格点击升序排列,选择最便宜的
  • 镜像选 Ubantu 最新版;不要免费加固,测完即毁

实例和镜像_价格升序 选择Ubantu镜像

存储云盘 ⇧

  • 调整为最低-20G
  • 仅勾选-随实例释放

存储云盘


网络和安全组 ⇧

  • 选择 按使用流量
    • 峰值调整为最低 1M
  • 其他选默认,之后可调整

带宽峰值_1M

管理设置 ⇧

  • 登录凭证
    • 登录名
    • 密钥对

创建密钥对,导入公钥

  • ssh-keygen -t rsa -b 4096 -C "
  • 终端bash命令 cat ~/.ssh/id_rsa.pub 粘贴内容

root_密钥对 导入密钥对 选择已有密钥对


其他实例选项 ⇧

  • 实例名称
  • 主机名

购买成功跳转到控制台-实例界面

实例名称_主机名


阿里云实例 ⇧

配置概要

使用 ssh 登录实例

  • 管理员打开 bash
  • 在实例界面复制公网 ip

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    ssh root@xxx.xxx.xxx.xx
    
    The authenticity of host 'xxx.xxx.xxx.xx (xxx.xxx.xxx.xx)' can't be established.
    ED25519 key fingerprint is SHA256:nb3c1Xzh5c6PTaCB1I5H6DN4U86JA3i1vJ8aIoXs30xsdW8+67CQ.
    This key is not known by any other names.
    Are you sure you want to continue connecting (yes/no/[fingerprint])? y
    Please type 'yes', 'no' or the fingerprint: yes
    Warning: Permanently added 'xxx.xxx.xxx.xx' (ED25519) to the list of known hosts.
    Welcome to Ubuntu 26.04.1 LTS (GNU/Linux 7.0.0-30-generic x86_64)
    
    * Documentation:  https://docs.ubuntu.com
    * Management:     https://landscape.canonical.com
    * Support:        https://ubuntu.com/pro
    
    System information as of Mon Sep 14 05:24:27 PM CST 2026
    
    System load:  0.15               Processes:             114
    Usage of /:   33.4% of 19.32GB   Users logged in:       0
    Memory usage: 15%                IPv4 address for eth0: xxx.xx.xxx.xxx
    Swap usage:   0%
    
    
    Expanded Security Maintenance for Applications is not enabled.
    
    0 updates can be applied immediately.
    
    1 additional security update can be applied with ESM Apps.
    Learn more about enabling ESM Apps service at https://ubuntu.com/esm
    
    
    The list of available updates is more than a week old.
    To check for new updates run: sudo apt update
    
    
    Welcome to Alibaba Cloud Elastic Compute Service !
  • 看到提示 Welcome to Alibaba Cloud Elastic Compute Service !

控制台-实例

使用完确认是否已停机,并且释放实例

  • 按量计费的实例停机后,云盘也会扣费,释放实例后才不会计费
  • 释放实例后,之前的所有操作记录都会消失

购买腾讯云 ⇧

  • 略

2. 服务器详细配置 ⇧

配置服务器

  • 登录
    • 不推荐使用 【用户名 + 密码】 登录,容易忘,不安全
    • 推荐使用 ssh-copy-id 上传公钥,使用 ssh 登录
  • 防火墙
    • 推荐开启 测试端口,如 3000、3001、5000、8000、8080
    • 自行选择是否开启重要端口,因为需要备案,如 80、443

登录 ⇧

  • ssh root@xxx.xxx.xxx.xx
    • 保存密码登录可使用 ssh-copy-id root@xxx.xxx.xxx.xx
  • 已登录,命令行无输入字符,按 Ctrl + D 或者输入 exit 退出登录

防火墙(安全组配置) ⇧

创建安全组 ⇧

  • 添加安全组配置
    • 添加入栈端口 3000、3001、5000、8000、8080等
    • 源 0.0.0.0/0
    • 测试端口无需备案,重要端口 80、443 需要备案

创建规则_添加端口 更换安全组


用户权限 ⇧

  • 用户权限
    • 推荐只在 root 用户里安装 Docker 即可
    • 每个应用创建一个 独立用户,并加入 docker 用户组,方便管理
    • 切记用 root 用户管理所有应用,容易被攻击
  • 发行版
    • 新手推荐使用 Ubuntu 次新版,因为教程多
    • 高手自行选择发行版,推荐 CentOS 次新版,因为稳定,适合生产环境

实操:ssh 用户登录保活 ⇧

root@server1: client_loop:send disconnect:Connection reset by peer

  • ssh 默认有超时时间,一段时间后会进入假死状态,终端卡住不能操作
  • 需要配置连接断开超时设置
  • 运行命令 vim /etc/ssh/sshd_config,在文件末尾添加如下内容:

    1
    2
    3
    4
    
    vim /etc/ssh/sshd_config
    # 添加
    ClientAliveInterval 300
    ClientAliveCountMax 5
  • 或者普通模式下 打 /Interval

    • 按 bb 连续向前移动单词到 # 左侧,按 x 删除#注释;
    • 移动到 0 按 s 删除并编辑,输入 300;
    • 按 Esc 退出编辑模式,输入 :wq 保存退出

重启 sshd 服务

1
systemctl daemon-reload

参考


root 用户安装 docker ⇧

如果时腾讯云的服务器,默认已经安装好 docker,可以直接使用,这里演示阿里云服务器安装 docker

  • 默认去阿里云的镜像源安装

    1
    2
    3
    4
    5
    6
    
    # 1. 更新 apt 包索引,并安装依赖包以允许 apt 通过 HTTPS 使用仓库:
    apt update
    apt install ca-certificates curl
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    chmod a+r /etc/apt/keyrings/docker.asc

设置 docker 的 apt 仓库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Add the repository to Apt sources:
tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

apt update

安装 docker 引擎、containerd 和 docker-compose-plugin 等

1
apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

验证docker镜像是否安装成功

1
2
3
4
docker --version
# Docker version 29.8.0, build 88096ef
docker run hello-world
# 运行 `docker run hello-world` 镜像源连接失败

配置镜像源

  • 使用 nano 编辑 /etc/docker/daemon.json 文件,添加国内镜像源

    1
    2
    3
    
    # 使用 nano 编辑 /etc/docker/daemon.json 文件
    nano --version
    nano /etc/docker/daemon.json

将以下内容完整粘贴到文件中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "registry-mirrors": [
    "https://docker.xuanyuan.me",
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io",
    "https://docker.mirrors.ustc.edu.cn/",
    "https://o3b9ba2s.mirror.aliyuncs.com",
    "https://reg-mirror.qiniu.com",
    "https://hub-mirror.c.163.com/"
  ]
}
  • 保存并退出编辑器(nano 编辑器请按 Ctrl+O 回车保存,Ctrl+X 退出)

重新加载配置并重启 Docker 服务:

1
2
3
4
5
systemctl daemon-reload
systemctl restart docker
# 再次运行
docker run hello-world
# Hello from Docker!

参考


添加 mangosteen 用户,并加入 docker 用户组,authorized_keys 权限操作 ⇧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# @root 用户下
# 添加 mangosteen 用户
adduser mangosteen

# 会要求输入密码,输入用户信息,按回车即可
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for mangosteen
Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n]

加入 docker 用户组

1
usermod -aG docker mangosteen
  • 按 Ctrl + D 退出root 用户,使用 mangosteen 用户登录

    1
    2
    3
    4
    
    ssh mangosteen@your_server_ip
    
    # 此时登录会报错
    # mangosteen@your_server_ip: Permission denied (publickey).

先再次进入 root 用户,修改 mangosteen 用户的 authorized_keys 权限

1
chmod 600 /home/mangosteen/.ssh/authorized_keys

也可以直接复制 root 用户的 authorized_keys 到 mangosteen 用户的 .ssh 目录下

1
2
3
4
5
6
7
8
9
cd ~/.ssh
ls
ll -a
# authorized_keys
# 查看内容
cat authorized_keys
# 需要先创建 .ssh 目录
mkdir -p /home/mangosteen/.ssh
cp ./authorized_keys /home/mangosteen/.ssh/authorized_keys
  • ls 按住 Alt + . 可以切换到上一个目录,按住 Alt + , 可以切换到下一个目录;查看当前目录下的文件
  • authorized_keys 这个文件是从 root 用户的 ~/.ssh/authorized_keys 复制过来的,里面是公钥内容

    • 目前 mangosteen 用户时无法访问 authorized_keys
    • 需要转交文件的控制权

      1
      2
      3
      4
      5
      6
      
      # chown mangosteen:mangosteen /home/mangosteen/.ssh/authorized_keys
      cd /home/mangosteen/.ssh/
      # 查看文件权限
      ll -a
      cd ..
      chown -R mangosteen:mangosteen .ssh
  • chown 命令是改变文件或目录的所有者和所属组

    • -R 递归修改目录下的所有文件和子目录
    • mangosteen:mangosteen 表示将所有权转交给 mangosteen 用户和 mangosteen 用户组
    • .ssh 是当前目录下的 .ssh 目录
  • 此时 运行 ls -a 可以看到 .ssh 目录的所有者和所属组已经变为 mangosteen

bash drwxr-xr-x 2 mangosteen mangosteen 4096 Sep 15 11:27 ./ drwxr-x--- 4 mangosteen mangosteen 4096 Sep 15 11:28 ../ -rw------- 1 mangosteen mangosteen 742 Sep 15 11:27 authorized_keys

此时重新退出再使用 mangosteen 用户登录,应该就可以成功登录了

  • 验证 mangosteen 用户是否可以使用 docker 命令

    1
    2
    
    docker ps
    docker run hello-world

购买创建实例后,初始化命令总结 ⇧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
ssh root@xxx.xxx.xxx.xx

# ssh 用户登录保活
vim /etc/ssh/sshd_config
# 编辑添加或修改 sshd_config 中内容
ClientAliveInterval 300
ClientAliveCountMax 5
# 重启 sshd 服务
systemctl daemon-reload

# root 用户安装 docker
apt update
apt install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

# 设置 docker 的 apt 仓库
tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

apt update

# 安装 docker 引擎、containerd 和 docker-compose-plugin 等
apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 验证docker镜像是否安装成功
docker --version
# Docker version 29.8.0, build 88096ef

# 配置镜像源
# 使用 nano 编辑 /etc/docker/daemon.json 文件
nano --version
nano /etc/docker/daemon.json
# 将以下内容完整粘贴到文件中
{
  "registry-mirrors": [
    "https://docker.xuanyuan.me", 
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io",
    "https://o3b9ba2s.mirror.aliyuncs.com",
    "https://docker.mirrors.ustc.edu.cn/",
    "https://reg-mirror.qiniu.com",
    "https://hub-mirror.c.163.com/"
  ]
}

# 重新加载配置并重启 Docker 服务:
systemctl daemon-reload
systemctl restart docker
# 验证
docker run hello-world
# Hello from Docker!

# 添加mangosteen用户
adduser mangosteen
# 加入docker用户组
usermod -aG docker mangosteen
# 添加移交root用户的authorized_keys权限给mangosteen用户
cd ~/.ssh
# ll -a
# cat authorized_keys
# 需要先创建 .ssh 目录
mkdir -p /home/mangosteen/.ssh
cp ./authorized_keys /home/mangosteen/.ssh/authorized_keys

# 查看文件权限
cd /home/mangosteen/.ssh/
ll -a
cd ..
chown -R mangosteen:mangosteen .ssh
# 退出 root 用户,使用 mangosteen 用户登录
exit
ssh mangosteen@your_server_ip
docker ps
docker run hello-world

参考


注意把不用的服务器停机或释放实例

3. 部署思路 ⇧

配置后端生产环境:步骤 ⇧

  • 准备一个新用户
  • 准备好 docker
  • 上传 Dockerfile
  • 上传源代码
  • 用 Dockerfile 构建运行环境
  • 在运行环境里运行源代码
  • 使用 Nginx 做转发
    • 源码会监听端口,假设 3000 不直接访问
    • Nginx 监听 80,把 80 的请求转发到 3000
    • 好处是 Nginx 可以做 负载均衡、零宕机时间

配置后端生产环境:版本更新 ⇧

  • 上传新 Dockerfile
  • 上传新源代码
  • 用新 Dockerfile 构建运行环境
  • 在新运行环境里运行新源代码
  • 使用 Nginx 做转发

配置前端生产环境 ⇧

前端哪有生产环境

  1. 将代码中的路径替换成 CDN 路径
  2. 上传打包好的 CSS JS 文件到 CDN,如 阿里云OSS、腾讯云COS
  3. 将 html 文件上传到后端服务器
  4. 使用 Nginx 处理 html 文件请求,转发到 CDN 上的 CSS JS 文件

写代码啦CDN

  • CDN 只处理静态文件,好处是可以加速访问,减少服务器压力,降低成本
  • CDN 节点也离用户更近,会针对用户的地理位置,分配最近的服务器

    1
    2
    3
    
    /assets/1-xxxx.js 
    # 修改资源前缀
    https://cdn.com/jirengu/assets/1-xxx.js

写代码啦CDN静态资源

具体实践过程之后处理,目前只处理后端


重点

  • 如何做到自动化?
  • 有哪些细节之前没有考虑过的?
  • 有哪些问题还没有提到?

检查后端服务端口是否被占用

  • 启动服务更换端口;或者重启 docker desktop;或者使用以下命令
  • Windows杀死占用端口程序的脚本:
  1. 使用 netstat-aon | findstr 3000 找到对应进程
  2. 使用 taskkill /F /PID 8888 杀死对应进程

4. 在 wsl 宿主机里部署(Windows/macOS/Linux都适用) ⇧

4.1 使用 window 作为生产环境 ⇧

添加后端路由根目录 ⇧

定义根目录 config/routes.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Rails.application.routes.draw do
  get '/', to: 'home#index'

  namespace :api do
    namespace :v1 do
      # /api/v1
      resources :validation_codes, only: [ :create ]
      resources :session, only: [ :create, :destroy ]
      resources :me, only: [ :show ]
      resources :items, exclude: [ :put ]
      resources :tags, exclude: [ :put ]
    end
  end
end

创建根目录对应的 Controller

  • 运行命令 bin/rails g controller home index

    1
    2
    3
    4
    5
    6
    
    bin/rails g controller home index
    
    #      create  app/controllers/home_controller.rb
    #       route  get "home/index"
    #      invoke  rspec
    #      create    spec/requests/home_spec.rb

app/controllers/home_controller.rb

1
2
3
4
5
6
7
class HomeController < ApplicationController
  def index
    render json : {
      message: 'Welcome!'
    }
  end
end

把源代码放到 windows 环境中,/workspaces/oh-my-env2 目录下,是否可以直接拷贝

  • 源代码目录下有些目录不能拷贝过去
    • .git 等以 . 开头的隐藏目录,等配置目录不需要拷贝
    • tmp/ 目录中部分文件也不需要拷贝
    • tmp/cache 不需要拷贝,其他文件是需要拷贝的
  • 使用 tar 命令打包源代码,上传到宿主机(windows)

生产环境打包脚本 pack_for_host.sh ⇧

新建脚本 scripts/pack_for_host.sh

  • 需要把打包的产物放到宿主机上,即数据卷目录 /workspaces/oh-my-env2/ 目录中
  • 使用 tar 命令打包源代码,上传到宿主机

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    #!/bin/bash
    
    ### 检查目标目录并动态设置基础路径
    if [ -d "/IdeaProjects" ]; then
    BASE_DIR="/IdeaProjects"
    elif [ -d "/workspaces" ]; then
    BASE_DIR="/workspaces"
    else
    echo "错误: 未找到 /workspaces 或 /IdeaProjects 目录,脚本退出。"
    exit 1
    fi
    echo "检测到有效目录,使用基础路径: ${BASE_DIR}"
    
    # 注意修改 oh-my-env 目录名为你的目录名
    dir=oh-my-env-2
    # 日期时间格式作为版本号
    time=$(date +'%Y%m%d-%H%M%S')
    # 压缩包文件名称
    dist=tmp/mangosteen-$time.tar.gz
    current_dir=$(dirname $0)
    deploy_dir=$BASE_DIR/$dir/mangosteen_deploy
    
    yes | rm tmp/mangosteen-*.tar.gz;
    yes | rm $deploy_dir/mangosteen-*.tar.gz;
    
    # 打包排除目录
    tar --exclude="tmp/cache/*" -czv -f $dist *
    mkdir -p $deploy_dir
    cp $current_dir/../config/host.Dockerfile $deploy_dir/Dockerfile
    cp $current_dir/setup_host.sh $deploy_dir/
    mv $dist $deploy_dir
    echo $time > $deploy_dir/version
    echo 'DONE!'
  • 需要设置目标打包的目录名称 oh-my-env-2

  • 日期时间格式作为 版本号 time=$(date +'%Y%m%d-%H%M%S')

    • 写入版本号文件中:echo $time > $deploy_dir/version
  • 设置打包目标路径文件 dist=tmp/mangosteen-$time.tar.gz 后缀中添加版本号

  • 打包排除目录 tar --exclude="tmp/cache/*" -czv -f $dist *

    • $dist 是打包目标路径文件
    • * 表示所有不以 . 开头的文件和目录
  • 创建打包目标路径目录 mkdir -p $deploy_dir

  • 添加脚本可执行权限 chmod +x $deploy_dir/setup_host.sh

  • 直接运行 bin/pack_for_host.sh 脚本即可打包源代码到宿主机

目前运行脚本会失败报错

  • 因为还需要准备文件 config/host.Dockerfile,在下一节中创建
  • 这个文件是给宿主机运行使用的

4.2 创建 host.Dockerfile 宿主机运行镜像 ⇧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#FROM ruby:3.1.2
FROM ruby:3.1.2-slim

ENV RAILS_ENV production
RUN mkdir /mangosteen
WORKDIR /mangosteen
ADD mangosteen-*.tar.gz ./
RUN bundle config mirror.https://rubygems.org https://mirrors.tuna.tsinghua.edu.cn/rubygems
RUN bundle config set --local without 'development test'
RUN bundle install
ENTRYPOINT exec bundle exec puma
  • 尝试使用版本 FROM ruby:3.1.2-slim
  • 设置环境变量 RAILS_ENV=production
  • 创建并设置工作代码目录 /mangosteen
    • WORKDIR /mangosteen 这一步十分重要,说明后续步骤的工作目录
  • 将打包后的源代码 解压到当前工作目录 ADD mangosteen-*.tar.gz ./
    • mangosteen-*.tar.gz 会识别类似 mangosteen-20260101-010101.tar.gz 格式的文件
    • ADD 命令会自动解压缩tar包内容到目录中
  • 设置镜像源 RUN bundle config mirror.https://rubygems.org https://mirrors.tuna.tsinghua.edu.cn/rubygems
    • 注意 https://gems.ruby-china.com 镜像源已废弃
  • 设置仅安装生产环境的包 RUN bundle config set --local without 'development test'
  • 安装依赖 RUN bundle install
  • 启动服务
    • 区别于开发环境 bundle exec rails server
    • 生产环境使用 puma 作为服务器:bundle exec puma
    • 使用 ENTRYPOINT exec 而不是 RUN,因为 RUN 会在构建镜像时执行,而 ENTRYPOINT 会在容器启动时执行
    • 这个 Dockerfile 目前只是构建一个可运行的 image,即 docker build 时构建
    • 这个 Dockerfile 在运行 docker run 时才运行此条命令
    • ENTRYPOINT 代表入口,即容器启动时执行的命令,仅当运行 docker run 时才会执行 bundle exec puma
    • 如果使用 RUN 会造成 docker build 永不结束,因为启动服务一直运行不会断开结束

参考


4.3 docker 构建部署启动脚本 setup_host.sh ⇧

bin/setup_host.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/bin/bash

DB_PASSWORD=123456
container_name=mangosteen-prod-1

version=$(cat mangosteen_deploy/version)

echo 'docker build ...'
docker build mangosteen_deploy -t mangosteen:$version
if [ "$(docker ps -aq -f name=^mangosteen-prod-1$)" ]; then
  echo 'docker rm ...'
  docker rm -f $container_name
fi
echo 'docker run ...'
docker run -d -p 3000:3000 --network=network1 -e DB_PASSWORD=$DB_PASSWORD --name=$container_name mangosteen:$version
echo 'docker exec db...'
docker exec -it $container_name bin/rails db:create db:migrate
echo 'DONE!'
  • 设置数据库密码 DB_PASSWORD=123456
  • 设置容器名称 container_name=mangosteen-prod-1
  • 读取版本号 version=$(cat mangosteen_deploy/version)
    • 即之前 pack_for_host.sh 脚本中生成的版本号文件 mangosteen_deploy/version 中的内容
  • 构建镜像 docker build mangosteen_deploy -t mangosteen:$version
    • mangosteen_deploy 是 pack_for_host.sh 脚本中创建的目录
  • 删除运行容器 docker rm -f $container_name
  • 启动容器 docker run -d -p 3000:3000 --network=network1 -e DB_PASSWORD=$DB_PASSWORD --name=$container_name mangosteen:$version
  • 创建表 docker exec -it $container_name bin/rails db:create db:migrate
  • 添加可执行权限 chmod +x bin/*.sh

4.4 总结流程 ⇧

  • 在 linux 宿主机里运行 pack_for_host.sh 脚本,把源代码打包,放到宿主机可访问的目录中
  • host.Dockerfile 是宿主机运行的镜像文件,放到宿主机可访问的目录中
  • 在 window 中运行 setup_host.sh 脚本,将构建启动 host.Dockerfile 中的镜像

添加可执行权限

1
2
3
4
5
# chmod +x bin/pack_for_host.sh
# chmod +x bin/setup_host.sh
chmod +x bin/*.sh
# 开始执行打包
bin/pack_for_host.sh
  • 查看目录 ls -l mangosteen_deploy/ 存在四个文件
    • Dockerfile 由host.Dockerfile复制而来
    • version 版本文件
    • mangosteen-*.tar.gz 源代码压缩包
    • setup_host.sh 给宿主机运行的脚本

宿主机运行 setup_host.sh 脚本

  • VSCode 打开 oh-my-env2/ 目录,注意不是直接打开 mangosteen_deploy/ 目录
  • 注意终端必须是 bash 需要另外配置;不可使用 CMD 或者 PowerShell

    1
    
    mangosteen_deploy/setup_host.sh
  • 查看宿主机 docker desktop 客户端中镜像数量

  • 找到 mangosteen:xxxxxx-xxxxxx 镜像

  • 运行 docker ps 查看容器是否启动成功

  • 如果未找到正在运行的容器使用 docker ps -a 查看容器哈希

    1
    2
    3
    4
    5
    6
    
    docker ps
    CONTAINER ID   IMAGE                                                                              COMMAND                   CREATED              STATUS              PORTS                                         NAMES
    922155153e8d   mangosteen:20260915-174001                                                         "/bin/sh -c 'exec bu…"   About a minute ago   Up About a minute   0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp   mangosteen-prod-1
    b5ae1c5338bc   56d78e0be2c2                                                                       "/bin/sh -c 'while s…"   3 days ago           Up 3 hours                                                        admiring_mahavira
    fc760ea83e62   vsc-oh-my-env-2-8356d6c3244aebf25d14130d26591ac417285100f20b188cc73d5dca696ebb75   "/bin/sh -c 'echo Co…"   3 days ago           Up 5 hours                                                        ecstatic_swirles
    5f72b78eb4c3   postgres:14                                                                        "docker-entrypoint.s…"   7 days ago           Up 9 hours          0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp   db-for-mangosteen

启动失败查看报错日志

  • Dockerfile 配置语法错误
  • docker logs 922155153e8d 查看容器日志
  • 找到是否包含 Missing secret_key_base

    • 需要一些加密配置
    • 需要另外配置生产环境数据库

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      
      ...
      2 warnings found (use docker --debug to expand):
      - JSONArgsRecommended: JSON arguments recommended for ENTRYPOINT to prevent unintended behavior related to OS signals (line 10)
      - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 3)
      
      View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/mlr1jbnne1sgym5qjbta1639z
      docker run ...
      922155153e8d22110624bdb22323da09ce53a041edaed8212e0f72237d26e505
      docker exec db...
      connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      connection to server on socket "/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      connection to server on socket "/tmp/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      Couldn't create 'mangosteen_1_production' database. Please check your configuration.
      bin/rails aborted!
      ActiveRecord::ConnectionNotEstablished: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      connection to server on socket "/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      connection to server on socket "/tmp/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      
      
      Caused by:
      PG::ConnectionBad: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      connection to server on socket "/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      connection to server on socket "/tmp/.s.PGSQL.5432" failed: No such file or directory
      Is the server running locally and accepting connections on that socket?
      
      Tasks: TOP => db:create
      (See full trace by running task with --trace)
      
      What's next:
      Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug mangosteen-prod-1
      Learn more at https://docs.docker.com/go/debug-cli/
      DONE!

询问 Gordon 修改文件 mangosteen_deploy/Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
FROM ruby:3.1.2-slim

ENV RAILS_ENV=production
RUN mkdir /mangosteen
RUN bundle config mirror.https://rubygems.org https://mirrors.tuna.tsinghua.edu.cn/rubygems
WORKDIR /mangosteen
ADD mangosteen-*.tar.gz ./
RUN bundle config set --local without 'development test'
RUN bundle install
ENTRYPOINT ["bundle", "exec", "puma"]

询问 Gordon 生成文件 mangosteen_deploy/docker-compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
version: '3.8'

services:
  db:
    image: postgres:15-alpine
    container_name: mangosteen-db
    environment:
      POSTGRES_PASSWORD: "123456"
      POSTGRES_DB: mangosteen_production
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - network1
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    build: .
    container_name: mangosteen-prod-1
    environment:
      RAILS_ENV: production
      DATABASE_URL: "postgresql://postgres:123456@db:5432/mangosteen_production"
      DB_PASSWORD: "123456"
    ports:
      - "3000:3000"
    networks:
      - network1
    depends_on:
      db:
        condition: service_healthy
    command: sh -c "bundle exec rails db:create db:migrate && bundle exec puma"

volumes:
  postgres_data:

networks:
  network1:
    driver: bridge

总结

  • 服务器
    • 购买、安装 Docker、创建用户、ssh 登录
  • 宿主机部署
    • 复制 Dockerfile 到宿主机、打包源代码到宿主机、写自动化脚本
    • 把 linux 系统中代码放到宿主机
    • 宿主机启动另外一个 linux 运行(容器)
    • 得到一个纯净的 ruby-3.1.2 环境
    • 之前开发环境的依赖太多,不需放在再生产环境中
  • 创建的两个脚本
    • pack_for_host.sh 打包脚本
    • setup_host.sh 宿主机部署脚本
  • 遗留问题:rails 启动报错
    • 缺少 secret_base_key
    • 生产环境数据库配置

不会用 vim 怎么办?

  • 不会用就千万别尝试,建议使用 nano 代替 vim 命令。

代码使用 Windows,是否适合 macOS / Linux 吗?

  • 在 docker 容器中 都是 Linux

·未完待续·

参考文章

相关文章


  • 作者: Joel
  • 文章链接:
  • 版权声明
  • 非自由转载-非商用-非衍生-保持署名