我的VPS服务部署记录

本文记录了在 VPS 服务器上部署各种服务的详细步骤,包括服务器初始化、Docker 环境配置、以及多个自托管服务的部署过程。

我的 VPS 使用的是 AlmaLinux 9 操作系统,所以以下操作都是基于 AlmaLinux 系统。

服务器设置

以下为初次登录 VPS 后的基础配置。AlmaLinux 9 默认开启 SELinux,使用 Nginx 反向代理时需在安装 Nginx 后执行后文中的 setsebool 命令。

设置主机名(可选)

hostnamectl set-hostname vps

配置 AlmaLinux 9 源(可选):先清理默认多份 .repo,再创建一份精简源文件(仅 BaseOS + AppStream)。

第一步:清理默认 yum 源文件

mkdir -p /etc/yum.repos.d/bak
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak/

第二步:新建精简 repo 文件

按机房位置选用下面一段,将 REPO_BASE 替换为对应域名后,整段复制执行(默认官方源、美国 AWS、国内阿里云三选一):

REPO_BASE=https://repo.almalinux.org
cat > /etc/yum.repos.d/almalinux.repo << EOF
[baseos]
name=AlmaLinux 9 - BaseOS
baseurl=$REPO_BASE/almalinux/9/BaseOS/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
[appstream]
name=AlmaLinux 9 - AppStream
baseurl=$REPO_BASE/almalinux/9/AppStream/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
EOF

清理缓存并重建缓存:

dnf clean all
dnf makecache

更新系统:

dnf update -y

安装常用软件:

dnf install git vim jq chrony -y

设置时钟同步(AlmaLinux 9 默认使用 chrony):

systemctl enable chronyd
systemctl start chronyd
chronyc makestep

设置时区为上海时区:

# 查看当前时区
timedatectl
# 设置时区为上海(Asia/Shanghai)
timedatectl set-timezone Asia/Shanghai
# 验证时区设置
timedatectl
date

配置 git:

git config --global user.name chensoul
git config --global user.email ichensoul@gmail.com

[可选] 使普通用户拥有 root 级 sudo 权限

将用户加入 wheel 组即可获得等同 root 的 sudo 权限(AlmaLinux 9 默认配置为 %wheel ALL=(ALL) ALL):

# 新建用户(若已有用户可只执行 usermod 与 passwd)
useradd -m -s /bin/bash chensoul
usermod -aG wheel chensoul
passwd chensoul
# 验证:切换为该用户后执行,应看到 (ALL) ALL 且 sudo whoami 输出 root
su - chensoul
sudo -l
sudo whoami # 应输出 root

之后可用 su - chensoul 或 SSH 登录该用户,建议配置好 SSH 密钥后再考虑禁用 root 密码登录。

[可选] 设置系统 Swap 交换分区

因为 vps 服务器的运行内存很小,所以这里先设置下 Swap 空间为 4G。

sudo fallocate -l 4G /swapfile && \
sudo dd if=/dev/zero of=/swapfile bs=1024 count=4194304 && \
sudo chmod 600 /swapfile && \
sudo mkswap /swapfile && \
sudo swapon /swapfile && \
echo "/swapfile swap swap defaults 0 0" | sudo tee -a /etc/fstab && \
sudo swapon --show && \
sudo free -h

[可选] 安全加固

在确认已有可用的 SSH 密钥登录或普通用户可 sudo 之前,不要禁用密码登录或 root 登录,以免被锁在机器外。

本机生成密钥对,并将公钥上传到 VPS,以便后续使用密钥登录。

# 生成 Ed25519 密钥对(推荐,比 RSA 更短更安全)
ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519_vps
# 提示输入 passphrase 可留空,或设置后每次使用密钥时需输入

上传公钥到 VPS(任选一种方式):

# 方式一:ssh-copy-id(需当前能密码登录 VPS)
ssh-copy-id -i ~/.ssh/id_ed25519_vps.pub root@your_vps_ip
# 若登录用户为普通用户:ssh-copy-id -i ~/.ssh/id_ed25519_vps.pub user@your_vps_ip
# 方式二:手动追加(将本机公钥内容追加到 VPS 的 authorized_keys)
# 本机查看公钥:cat ~/.ssh/id_ed25519_vps.pub
# 在 VPS 上执行:mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo "粘贴公钥内容" >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys

上传后务必先测试密钥登录是否成功(新开一个终端窗口,密码登录不要关),再在服务端禁用密码登录:

ssh -i ~/.ssh/id_ed25519_vps root@your_vps_ip
# 若使用非默认密钥路径,可在 ~/.ssh/config 中配置 Host 与 IdentityFile
# 备份并设置:禁用密码登录、启用公钥登录
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/; s/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
grep -q '^PasswordAuthentication ' /etc/ssh/sshd_config || echo 'PasswordAuthentication no' | sudo tee -a /etc/ssh/sshd_config
grep -q '^PubkeyAuthentication ' /etc/ssh/sshd_config || echo 'PubkeyAuthentication yes' | sudo tee -a /etc/ssh/sshd_config
sudo systemctl restart sshd
# 仅允许 root 使用密钥登录(推荐)
sudo sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
grep -q '^PermitRootLogin ' /etc/ssh/sshd_config || echo 'PermitRootLogin prohibit-password' | sudo tee -a /etc/ssh/sshd_config
sudo systemctl restart sshd
# 禁止 root 登录,仅用普通用户 + sudo
sudo sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
grep -q '^PermitRootLogin ' /etc/ssh/sshd_config || echo 'PermitRootLogin no' | sudo tee -a /etc/ssh/sshd_config
sudo systemctl restart sshd
dnf install epel-release -y
dnf install fail2ban fail2ban-firewalld -y
systemctl enable fail2ban --now
# 默认会保护 sshd,可查看状态:fail2ban-client status sshd

安装 Nginx

AlmaLinux 9 默认仓库不含 Nginx 或版本较旧,需要额外添加 Nginx 官方 yum 源,再用 dnf install nginx 安装(本文采用包管理安装,不从源码编译)。

# 添加 Nginx 官方 yum 源(适用于 RHEL 9 / AlmaLinux 9)
cat > /etc/yum.repos.d/nginx.repo << 'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=https://nginx.org/packages/rhel/9/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
# 若系统已装 AlmaLinux 自带的 nginx-core,先卸载以免与官方 nginx 包冲突
dnf remove -y nginx-core nginx-filesystem nginx-mod-* 2>/dev/null || true
dnf install nginx -y
systemctl enable nginx
systemctl start nginx

打开防火墙端口:

dnf install firewalld -y
firewall-cmd --zone=public --permanent --add-service=http
firewall-cmd --zone=public --permanent --add-service=https
firewall-cmd --reload

使用反向代理需要打开 SELinux 网络访问权限:

setsebool -P httpd_can_network_connect on

安装并生成证书

域名托管在 CloudFlare,使用 acme.sh 通过 DNS API 自动申请和续期 SSL 证书。参考 acme.sh DNS API 文档

准备工作

  1. 在 CloudFlare 获取 API Token(推荐)或 Global API Key;使用 Token 时建议同时设置 Account ID
  2. 创建 SSL 证书存储目录
# 邮箱统一使用变量(Git、acme.sh 注册与 CloudFlare 共用)
export GIT_EMAIL=ichensoul@gmail.com
# 创建 SSL 证书目录
mkdir -p /etc/nginx/ssl
# 安装 acme.sh
curl https://get.acme.sh | sh -s email=$GIT_EMAIL
# 使用 API Token(推荐)+ Account ID
export CF_Token="your_api_token"
export CF_Account_ID="your_account_id"
# 或使用 Global API Key:export CF_Key=xxx(邮箱会使用上面的 GIT_EMAIL)
# 申请证书(支持通配符域名)
~/.acme.sh/acme.sh --issue --server letsencrypt --dns dns_cf -d chensoul.cc -d '*.chensoul.cc'
# 复制证书到 Nginx 目录
cp ~/.acme.sh/chensoul.cc_ecc/{chensoul.cc.cer,chensoul.cc.key,fullchain.cer,ca.cer} /etc/nginx/ssl/
# 安装证书并设置自动续期
~/.acme.sh/acme.sh --installcert -d chensoul.cc -d *.chensoul.cc \
--cert-file /etc/nginx/ssl/chensoul.cc.cer \
--key-file /etc/nginx/ssl/chensoul.cc.key \
--fullchain-file /etc/nginx/ssl/fullchain.cer \
--ca-file /etc/nginx/ssl/ca.cer \
--reloadcmd "nginx -s reload"

注意:证书会自动续期,续期命令已添加到 crontab 中。

Docker 安装和配置

一键安装docker和docker compose:

bash <(curl -sSL https://linuxmirrors.cn/docker.sh)

启动 Docker 服务:

systemctl enable docker
systemctl start docker
# 验证安装
docker --version
docker ps

查看docker配置文件,默认会给我们配置刚才选择的镜像加速地址

cat /etc/docker/daemon.json

设置 iptables 允许流量转发:

iptables -P FORWARD ACCEPT

参考 Best Practice: Use a Docker network,创建一个自定义的网络:

docker network create vps

查看 docker 网络:

docker network ls
NETWORK ID NAME DRIVER SCOPE
68f4aeaa57bd bridge bridge local
6a96b9d8617e vps bridge local
4a8679e35f4d host host local
ba21bef23b04 none null local

注意:bridge、host、none 是内部预先创建的网络。

是否需要暴露端口:需要。Nginx 装在宿主机上,通过 proxy_pass http://127.0.0.1:端口 反代到容器,因此各服务的 compose 里要写 ports 映射到宿主机。为减少误暴露,建议只绑定本机:写成 127.0.0.1:3003:3000 而不是 3003:3000,这样外网无法直连该端口;防火墙只放行 80/443 即可,其余端口仅本机访问。

服务部署

Postgres

PostgreSQL 是一个功能强大的开源关系型数据库管理系统,作为其他服务的数据存储后端。

创建 postgres.yaml 文件:

services:
postgres:
image: postgres:18
restart: always
ports:
- "127.0.0.1:5435:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: vps@027!
TZ: Asia/Shanghai
healthcheck:
test:
["CMD", "pg_isready", "-U", "postgres", "-d", "postgres"]
interval: 5s
timeout: 10s
retries: 5
networks:
- vps
networks:
vps:
external: true

启动服务:

docker compose -f postgres.yaml up -d
# 查看日志
docker logs -f postgres
# 验证数据库连接
docker exec -it postgres psql -U postgres -d postgres

Umami

Umami 是一个简单、快速、注重隐私的网站分析工具,可以作为 Google Analytics 的开源替代品。

  1. 在 postgres 容器创建 umami 数据库:
docker exec -it postgres psql -U postgres
CREATE DATABASE umami OWNER postgres;
  1. 通过 docker compose 安装,创建 umami.yaml
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami
ports:
- "127.0.0.1:3003:3000"
environment:
DATABASE_URL: postgresql://postgres:vps@027!@postgres:5432/umami
DATABASE_TYPE: postgresql
HASH_SALT: vps@2025
TRACKER_SCRIPT_NAME: random-string.js
TZ: "Asia/Shanghai"
GENERIC_TIMEZONE: "Asia/Shanghai"
networks:
- vps
restart: always
networks:
vps:
external: true

参考 https://eallion.com/umami/,Umami 的默认跟踪代码是被大多数的广告插件屏蔽的,被屏蔽了你就统计不到访客信息了。如果需要反屏蔽,需要在 docker compose.yml 文件中添加环境变量:TRACKER_SCRIPT_NAME,如:

environment:
TRACKER_SCRIPT_NAME: random-string.js

然后获取到的跟踪代码的 src 会变成:

srcipt.js => random-string.js

启动:

docker compose -f umami.yaml up -d
docker logs -f umami
  1. 设置自定义域名:umami.chensoul.cc

  2. 配置 nginx 配置文件 /etc/nginx/conf.d/umami.conf

server {
listen 80;
listen [::]:80;
server_name umami.chensoul.cc;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name umami.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:3003;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
expires 12h;
}
}
  1. 重新加载 Nginx 配置并访问服务:
nginx -t
nginx -s reload

访问 https://umami.chensoul.cc/,默认用户名和密码为 admin/umami。登录之后,请立即修改密码,并添加要统计的网站。

Memos

Memos 是一个开源的、自托管的备忘录和知识管理系统,支持 Markdown、标签、搜索等功能。

  1. 在 postgres 容器创建 memos 数据库:
docker exec -it postgres psql -U postgres
CREATE DATABASE memos OWNER postgres;
  1. 通过 docker compose 安装,创建 memos.yaml
services:
memos:
image: neosmemo/memos:0.18.2
container_name: memos
environment:
- MEMOS_DRIVER=postgres
- MEMOS_DSN=postgres://postgres:vps@027!@postgres:5432/memos?sslmode=disable
volumes:
- ~/.memos/:/var/opt/memos
ports:
- '127.0.0.1:5230:5230'
networks:
- vps
networks:
vps:
external: true

版本说明

  • 0.21.0 版本:支持 Mermaid、分享功能
  • 0.22.0 版本:去掉了分享功能
  • 0.24.0 版本:支持 Shortcuts
  • 0.24.1 版本:改变了布局
  1. 启动服务:
docker compose -f memos.yaml up -d
docker logs -f memos
  1. 配置 nginx 配置文件 /etc/nginx/conf.d/memos.conf
server {
listen 80;
listen [::]:80;
server_name memos.chensoul.cc;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name memos.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:5230;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

重新加载 Nginx 配置并访问服务:

nginx -s reload

访问 https://memos.chensoul.cc/ ,默认用户名和密码为 admin/memos。登录之后,请立即修改密码。

N8n

N8n 是一个开源的工作流自动化工具,可以通过可视化界面创建自动化工作流,连接各种服务和 API。

  1. 在 postgres 容器创建 n8n 数据库:
docker exec -it postgres psql -U postgres
CREATE DATABASE n8n OWNER postgres;
  1. 通过 docker compose 安装,创建 n8n.yaml
services:
n8n:
image: n8nio/n8n
container_name: n8n
restart: always
environment:
- NODE_ENV=production
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=postgres
- DB_POSTGRESDB_PASSWORD=vps@027!
- TZ="Asia/Shanghai"
- GENERIC_TIMEZONE="Asia/Shanghai"
- N8N_DEFAULT_LOCALE=zh-CN
# 是否启用诊断和个性化推荐
- N8N_DIAGNOSTICS_ENABLED=false
- N8N_PERSONALIZATION_ENABLED=false
# 是否在启动时重新安装缺失的包
- N8N_REINSTALL_MISSING_PACKAGES=true
- WEBHOOK_URL=https://n8n.chensoul.cc/
- N8N_HOST=n8n.chensoul.cc
- N8N_ENCRYPTION_KEY=2cc5b5f9e31b43ff3817c1d04e5fa735
ports:
- '127.0.0.1:5678:5678'
networks:
- vps
networks:
vps:
external: true
  1. 启动服务:
docker compose -f n8n.yaml up -d
docker logs -f n8n
  1. 配置 nginx 配置文件 /etc/nginx/conf.d/n8n.conf
server {
listen 80;
listen [::]:80;
server_name n8n.chensoul.cc;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name n8n.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:5678;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection 'upgrade';
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Origin 'https://n8n.chensoul.cc';
access_log /var/log/nginx/n8n.log combined buffer=128k flush=5s;
}
}

故障排查:如果出现 Origin header does NOT match the expected origin. (Origin: "n8n.chensoul.cc", Expected: "127.0.0.1:5678") 错误,需要在 nginx 配置里添加 proxy_set_header Origin 'https://n8n.chensoul.cc';

  1. 重新加载 Nginx 配置并访问服务:
nginx -t
nginx -s reload
  1. 创建工作流(Workflows):

参考这篇文章 http://stiles.cc/archives/237/ ,目前我配置了以下 workflows,实现了 github、douban、rss、memos 同步到 Telegram。

workflows 参考:

Linkding

一款自托管的书签管理器,设计简洁、快速且易于设置。

创建数据库

docker exec -it postgres psql -U postgres
CREATE DATABASE linkding OWNER postgres;

创建 docker-compose 文件 linkding.yaml

services:
linkding:
#image: woohoodai/linkding-cn:latest
image: sissbruecker/linkding
container_name: linkding
restart: unless-stopped
environment:
- LD_SUPERUSER_NAME=admin
- LD_SUPERUSER_PASSWORD=E2KWxxxx
- LD_DB_ENGINE=postgres
- LD_DB_DATABASE=linkding
- LD_DB_HOST=postgres
- LD_DB_USER=postgres
- LD_DB_PASSWORD=vps@027!
ports:
- "127.0.0.1:9090:9090"
networks:
- vps
networks:
vps:
external: true

启动服务:

docker compose -f linkding.yaml up -d
docker logs -f linkding

配置 nginx 配置文件 /etc/nginx/conf.d/linkding.conf

server {
listen 80;
listen [::]:80;
server_name linkding.chensoul.cc;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name linkding.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:9090;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection 'upgrade';
proxy_set_header Upgrade $http_upgrade;
access_log /var/log/nginx/linkding.log combined buffer=128k flush=5s;
}
}

重新加载 Nginx 配置并访问服务:

nginx -t
nginx -s reload

登录之后进行设置 Auto Tagging:

youtube.com youtube video
github.com github
bilibili.com bilibili video

完整的备份:

docker exec -it linkding python manage.py full_backup /etc/linkding/data/backup.zip
docker cp linkding:/etc/linkding/data/backup.zip backup.zip

使用:

start=$(date -v-1w -v-mon +%Y-%m-%dT00:00:00Z)
end=$(date -v-mon +%Y-%m-%dT00:00:00Z)
curl -sS -H "Authorization: Token ${LINKDING_TOKEN}" \
"https://linkding.chensoul.cc/api/bookmarks/?limit=100" \
| jq -r --arg start "$start" --arg end "$end" '
.results[]?
| select(.date_added >= $start and .date_added < $end)
| "- [" + (.title // "无标题") + "](" + .url + ")"
'

Bark

Bark 是一个 iOS 应用,允许你向 iPhone 推送自定义通知。通过自建服务器,可以实现消息推送的完全控制。

项目地址:https://github.com/Finb/Bark

  1. 服务端使用 Docker 安装:
docker run -dt --name bark -p 127.0.0.1:3020:8080 -v ~/.bark/data://data finab/bark-server
  1. 配置 nginx 配置文件 /etc/nginx/conf.d/bark.conf
server {
listen 80;
listen [::]:80;
server_name bark.chensoul.cc;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name bark.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:3020;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
  1. 重新加载 Nginx 配置:
nginx -t
nginx -s reload
  1. 在 iOS 上安装 Bark 应用,添加私有服务器:`http://bark.chensoul.cc

  2. 测试推送功能:

# 替换 <device_key> 为你的设备密钥
curl -X "POST" "https://bark.chensoul.cc/<device_key>" \
-H 'Content-Type: application/json; charset=utf-8' \
-d '{
"body": "这是一个测试",
"title": "Test Title",
"badge": 1,
"category": "myNotificationCategory",
"icon": "https://www.usememos.com/logo-rounded.png",
"group": "test"
}'

Uptime Kuma

Uptime Kuma 是一个易于使用的自托管监控工具,可以监控网站和服务的可用性。

项目地址:https://github.com/louislam/uptime-kuma

使用 docker compose 部署,创建 uptime.yaml

services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
volumes:
- ~/.uptime-kuma://app/data
ports:
- "127.0.0.1:3001:3001"
restart: always
networks:
- vps
networks:
vps:
external: true

启动服务:

docker compose -f uptime.yaml up -d
docker logs -f uptime-kuma

配置 nginx 配置文件 /etc/nginx/conf.d/uptime.conf

server {
listen 80;
listen [::]:80;
server_name uptime.chensoul.cc;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name uptime.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

重新加载 Nginx 配置并访问服务:

nginx -t
nginx -s reload

访问 https://uptime.chensoul.cc/,首次访问需要设置管理员账号。

升级服务

docker compose -f uptime.yaml down
docker pull louislam/uptime-kuma:1
docker compose -f uptime.yaml up -d

Artalk

在 postgres 容器创建 artalk 数据库:

docker exec -it postgres psql -U postgres
CREATE DATABASE artalk OWNER postgres;

通过 docker compose 安装,创建 artalk.yaml

services:
artalk:
container_name: artalk
image: artalk/artalk-go
restart: unless-stopped
ports:
- "127.0.0.1:23366:23366"
environment:
- TZ=Asia/Shanghai
- ATK_LOCALE=zh-CN
- ATK_SITE_DEFAULT=Chensoul Blog
- ATK_SITE_URL=https://blog.chensoul.cc
- ATK_TRUSTED_DOMAINS=https://blog.chensoul.cc http://localhost:1313
- ATK_DB_TYPE=pgsql
- ATK_DB_HOST=postgres
- ATK_DB_PORT=5432
- ATK_DB_NAME=artalk
- ATK_DB_USER=postgres
- ATK_DB_PASSWORD=vps@027!
- ATK_ADMIN_NOTIFY_BARK_ENABLED=true
- ATK_ADMIN_NOTIFY_BARK_SERVER=https://bark.chensoul.cc/AXWM5ZKnKa9Lp3rijBsVDm
- ATK_ADMIN_NOTIFY_TELEGRAM_ENABLED=true
- ATK_ADMIN_NOTIFY_TELEGRAM_API_TOKEN=8286091453:AAE2pucQXfliz_QGac1zVZEFIrrxVOLe938
- ATK_ADMIN_NOTIFY_TELEGRAM_RECEIVERS=[-1001632154815]
networks:
- vps
networks:
vps:
external: true

启动服务:

docker compose -f artalk.yaml up -d
docker logs -f artalk

配置 nginx 配置文件 /etc/nginx/conf.d/artalk.conf

server {
listen 80;
listen [::]:80;
server_name artalk.chensoul.cc;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name artalk.chensoul.cc;
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/chensoul.cc.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:23366;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

重新加载 Nginx 配置:

nginx -t
nginx -s reload

执行命令创建管理员账户:

docker exec -it artalk artalk admin

浏览器输入 http://artalk.chensoul.cc 进入 Artalk 后台登录界面。

服务升级

使用 Watchtower 自动更新 Docker 镜像与容器。Watchtower 会定期检查容器镜像更新,并自动重启容器使用新镜像。

# 每天凌晨3点05分更新
docker run -d \
--name watchtower \
--restart always \
-e TZ=Asia/Shanghai \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
--cleanup \
-s "0 5 3 * * *"

注意:Watchtower 会自动更新所有容器。如果只想更新特定容器,可以使用标签过滤功能。

数据库备份和恢复

定期备份数据库是保障数据安全的重要措施。以下脚本实现了自动备份 PostgreSQL 数据库并推送到 Git 仓库。

PostgreSQL 数据库备份

备份脚本:/opt/vps-backup/scripts/backup_database.sh

#!/bin/bash
set -e
CONTAINER_NAME="postgres"
BACKUP_BASE="/opt/vps-backup"
BACKUP_DIR="$BACKUP_BASE/postgres"
BACKUP_POSTFIX=$(date +%Y%m%d%H) # 如 2026030316
# n8n 数据量太大了,只需要部署 workflow 文件和凭证即可
DATABASES=(memos umami linkding artalk)
mkdir -p "$BACKUP_DIR"
for DB_NAME in "${DATABASES[@]}"; do
BACKUP_FILE="${DB_NAME}_${BACKUP_POSTFIX}.sql"
docker exec "$CONTAINER_NAME" pg_dump -U postgres "$DB_NAME" > "$BACKUP_DIR/$BACKUP_FILE"
echo "DB $DB_NAME backup success: $BACKUP_FILE"
done
# 删除超过1天的备份文件
find "$BACKUP_DIR" -name "*.sql" -type f -mtime +1 -delete
cd "$BACKUP_BASE"
git pull
git add .
git commit -m "backup postgres for $BACKUP_POSTFIX"
git push origin main

查询某库内各表记录数

在容器内用 psql 查指定库中每个表的行数(以下为估算值,来自 pg_stat_user_tables,大表足够参考):

CONTAINER_NAME="postgres"
DB_NAME="n8n" # 改成要查的库名
docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -c "
SELECT schemaname AS schema,
relname AS table_name,
n_live_tup AS row_estimate
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;
"

若要精确行数(大表较慢),可先生成再执行:

docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -t -A -c "
SELECT 'SELECT count(*) AS \"' || relname || '\" FROM ' || quote_ident(schemaname) || '.' || quote_ident(relname) || ';'
FROM pg_stat_user_tables;
"

把输出在 psql -d "$DB_NAME" 里执行即可。

清理表数据

在确认无需保留数据的前提下,在目标库中执行:

执行前建议先备份或确认库名、表名无误。

数据库恢复

按备份时间戳恢复(备份目录 postgres/,文件命名:<库名>_<YYYYMMDDHH>.sql)。将 BACKUP_POSTFIX 改为要恢复的那次备份时间(如 2026030316),在备份仓库根或指定目录下执行。

若目标库已存在:会先禁止新连接、断开已有连接、删库、建空库再导入,当前库数据会被覆盖,请确认后再执行。若仍报 database "xxx" is being accessed by other users,请先停止使用该库的容器(如 docker stop memos)再执行恢复。

CONTAINER_NAME="postgres"
BACKUP_DIR="/opt/vps-backup/postgres"
BACKUP_POSTFIX="2026030316" # 改为实际备份时间
DATABASES=(memos umami n8n linkding artalk)
for DB_NAME in "${DATABASES[@]}"; do
F="${DB_NAME}_${BACKUP_POSTFIX}.sql"
[ -f "$BACKUP_DIR/$F" ] || { echo "跳过(文件不存在): $F"; continue; }
docker cp "$BACKUP_DIR/$F" "$CONTAINER_NAME:/tmp/$F"
# 禁止新连接并断开已有连接后再删库
docker exec "$CONTAINER_NAME" psql -U postgres -d postgres -c "ALTER DATABASE $DB_NAME CONNECT LIMIT 0;" 2>/dev/null || true
docker exec "$CONTAINER_NAME" psql -U postgres -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DB_NAME' AND pid <> pg_backend_pid();" 2>/dev/null || true
sleep 1
docker exec "$CONTAINER_NAME" psql -U postgres -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;"
docker exec "$CONTAINER_NAME" psql -U postgres -d postgres -c "CREATE DATABASE $DB_NAME OWNER postgres;"
docker exec "$CONTAINER_NAME" psql -U postgres -d "$DB_NAME" -f "/tmp/$F"
docker exec "$CONTAINER_NAME" rm -f "/tmp/$F"
echo "已恢复: $DB_NAME"
done

只恢复单个库时(同样会先断连接、删库再建库再导入):

BACKUP_POSTFIX="2026030316"
DB_NAME=memos
BACKUP_DIR="/opt/vps-backup/postgres"
docker cp "$BACKUP_DIR/${DB_NAME}_${BACKUP_POSTFIX}.sql" postgres:/tmp/
docker exec postgres psql -U postgres -d postgres -c "ALTER DATABASE $DB_NAME CONNECT LIMIT 0;" 2>/dev/null || true
docker exec postgres psql -U postgres -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$DB_NAME' AND pid <> pg_backend_pid();" 2>/dev/null || true
sleep 1
docker exec postgres psql -U postgres -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;"
docker exec postgres psql -U postgres -d postgres -c "CREATE DATABASE $DB_NAME OWNER postgres;"
docker exec postgres psql -U postgres -d "$DB_NAME" -f /tmp/${DB_NAME}_${BACKUP_POSTFIX}.sql
docker exec postgres rm -f /tmp/${DB_NAME}_${BACKUP_POSTFIX}.sql

N8n 工作流备份和恢复

备份脚本:/opt/vps-backup/scripts/backup_n8n.sh

#!/bin/bash
set -e
BACKUP_BASE="/opt/vps-backup"
BACKUP_DIR="$BACKUP_BASE/n8n"
WORKFLOWS_DIR="$BACKUP_DIR/workflows"
mkdir -p "$WORKFLOWS_DIR"
# 导出工作流到容器临时目录,再拷出并按名称重命名
docker exec -u node n8n n8n export:workflow --backup --output=./backup_workflows
docker cp n8n:/home/node/backup_workflows/. "$WORKFLOWS_DIR/"
for f in "$WORKFLOWS_DIR"/*; do
[ -f "$f" ] || continue
name=$(jq -r '.name' "$f" 2>/dev/null)
[ -n "$name" ] && [ "$name" != "null" ] && mv "$f" "$WORKFLOWS_DIR/${name}.json"
done
# 导出凭证到当日目录
docker exec -u node n8n n8n export:credentials --all --output=./credentials.json
docker cp n8n:/home/node/credentials.json "$BACKUP_DIR/credentials.json"
# 删除超过1天的备份日期目录
find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +1 -exec rm -rf {} \;
cd "$BACKUP_BASE"
git pull
git add .
git commit -m "backup n8n"
git push origin main

恢复工作流

BACKUP_DIR="/opt/vps-backup/n8n"
docker cp "$BACKUP_DIR/workflows" n8n:/home/node/restore_workflows
docker exec -u node n8n n8n import:workflow --separate --input=/home/node/restore_workflows
docker exec -u node n8n rm -rf /home/node/restore_workflows

恢复凭证(需与备份时使用同一 N8N_ENCRYPTION_KEY,否则无法解密):

docker cp "$BACKUP_DIR/$DATE/credentials.json" n8n:/home/node/
docker exec -u node n8n n8n import:credentials --input=/home/node/credentials.json
docker exec -u node n8n rm -f /home/node/credentials.json

说明:若当前库中已有同 ID 的工作流或凭证,导入时会覆盖。恢复后若 n8n 已在运行,工作流/凭证变更会生效,必要时可重启容器。

定时任务

使用 crontab 设置定时任务,实现自动化运维。可手动 crontab -e 添加,或用下面命令一次性写入(不重复添加):

# 写入 crontab(若已有同类任务会重复,建议先 crontab -l 查看后选用其一方式)
( crontab -l 2>/dev/null | grep -v 'acme.sh --cron' | grep -v 'backup_database.sh' | grep -v 'backup_n8n.sh'; \
echo '58 1 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null'; \
echo '0 */3 * * * sh /opt/vps-backup/scripts/backup_database.sh'; \
echo '0 2 * * * sh /opt/vps-backup/scripts/backup_n8n.sh'; \
) | crontab -
# 查看当前任务
crontab -l

说明:SSL 证书续期(每天 1:58)、数据库备份(每 3 小时)、N8n 工作流备份(每天 2:00)。时钟同步由 chronyd 常驻,无需 crontab。

附录

AlmaLinux 9 一键初始化脚本

将下文脚本保存为 almalinux9-setup.sh,按需修改顶部变量或通过环境变量传入,以 root 执行 bash almalinux9-setup.sh。涵盖系统设置、安全加固、Nginx、SSL(acme.sh)、Docker;可选步骤均由变量控制。安全加固(SSH_KEY_ONLY 等)请确认密钥或普通用户可用后再开启;SSL 申请需 CloudFlare 凭证与 DOMAIN,未配置则只安装 acme.sh 与证书目录。

#!/usr/bin/env bash
# AlmaLinux 9 一键初始化:系统设置 + Nginx + SSL(acme.sh)+ Docker
# 用法:修改变量或环境变量后执行 bash almalinux9-setup.sh(需 root)
set -e
# ---------- 系统设置 ----------
HOSTNAME="${HOSTNAME:-vps}"
MIRROR="${MIRROR:-}"
GIT_USER="${GIT_USER:-chensoul}"
GIT_EMAIL="${GIT_EMAIL:-ichensoul@gmail.com}"
CREATE_USER="${CREATE_USER:-}"
SWAP_GB="${SWAP_GB:-0}"
SSH_KEY_ONLY="${SSH_KEY_ONLY:-no}"
PERMIT_ROOT="${PERMIT_ROOT:-prohibit-password}"
INSTALL_FAIL2BAN="${INSTALL_FAIL2BAN:-no}"
# ---------- Nginx / SSL / Docker ----------
INSTALL_NGINX="${INSTALL_NGINX:-yes}"
INSTALL_SSL="${INSTALL_SSL:-yes}"
INSTALL_DOCKER="${INSTALL_DOCKER:-yes}"
DOMAIN="${DOMAIN:-}"
CF_Token="${CF_Token:-}"
CF_Account_ID="${CF_Account_ID:-}"
CF_Key="${CF_Key:-}"
CF_Email="${CF_Email:-$GIT_EMAIL}" # 不设则与 GIT_EMAIL 一致
[[ $(id -u) -eq 0 ]] || { echo "请使用 root 运行"; exit 1; }
echo "[1/11] 设置主机名 ..."
[[ -z "$HOSTNAME" ]] || hostnamectl set-hostname "$HOSTNAME"
echo "[2/11] 配置 dnf 源(清理默认并创建精简 repo) ..."
case "$MIRROR" in
"") REPO_BASE="https://repo.almalinux.org" ;;
aliyun) REPO_BASE="https://mirrors.aliyun.com" ;;
aws-bos) REPO_BASE="https://bos.aws.repo.almalinux.org" ;;
aws-den) REPO_BASE="https://den.aws.repo.almalinux.org" ;;
*) echo "未知 MIRROR: $MIRROR"; exit 1 ;;
esac
mkdir -p /etc/yum.repos.d/bak
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak/ 2>/dev/null || true
cat > /etc/yum.repos.d/almalinux.repo << REPOEOF
[baseos]
name=AlmaLinux 9 - BaseOS
baseurl=$REPO_BASE/almalinux/9/BaseOS/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
[appstream]
name=AlmaLinux 9 - AppStream
baseurl=$REPO_BASE/almalinux/9/AppStream/\$basearch/os/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9
REPOEOF
dnf clean all && dnf makecache
echo "[3/11] 更新系统并安装常用软件 ..."
dnf update -y
dnf install -y git vim jq chrony
echo "[4/11] 时钟同步与时区 ..."
systemctl enable chronyd && systemctl start chronyd
chronyc -a makestep || true
timedatectl set-timezone Asia/Shanghai
echo "[5/11] Git 全局配置 ..."
git config --global user.name "$GIT_USER"
git config --global user.email "$GIT_EMAIL"
echo "[6/11] 可选:创建 sudo 用户 ..."
if [[ -n "$CREATE_USER" ]]; then
if ! id "$CREATE_USER" &>/dev/null; then
useradd -m -s /bin/bash "$CREATE_USER"
usermod -aG wheel "$CREATE_USER"
echo "请为 $CREATE_USER 设置密码:"
passwd "$CREATE_USER"
else
usermod -aG wheel "$CREATE_USER"
echo "用户 $CREATE_USER 已存在,已加入 wheel。"
fi
fi
echo "[7/11] 可选:Swap ..."
if [[ "$SWAP_GB" -gt 0 ]] && [[ ! -f /swapfile ]]; then
fallocate -l "${SWAP_GB}G" /swapfile
chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
swapon --show
fi
echo "[8/11] 可选:SSH 加固与 fail2ban ..."
if [[ "$SSH_KEY_ONLY" == "yes" || "$PERMIT_ROOT" != "yes" ]]; then
cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
if [[ "$SSH_KEY_ONLY" == "yes" ]]; then
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/; s/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
grep -q '^PasswordAuthentication ' /etc/ssh/sshd_config || echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config
grep -q '^PubkeyAuthentication ' /etc/ssh/sshd_config || echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config
fi
if [[ "$PERMIT_ROOT" != "yes" ]]; then
sed -i "s/^#*PermitRootLogin.*/PermitRootLogin $PERMIT_ROOT/" /etc/ssh/sshd_config
grep -q '^PermitRootLogin ' /etc/ssh/sshd_config || echo "PermitRootLogin $PERMIT_ROOT" >> /etc/ssh/sshd_config
fi
systemctl restart sshd
fi
if [[ "$INSTALL_FAIL2BAN" == "yes" ]]; then
dnf install -y epel-release
dnf install -y fail2ban fail2ban-firewalld
systemctl enable fail2ban --now
fi
echo "[9/11] Nginx ..."
if [[ "$INSTALL_NGINX" == "yes" ]]; then
if [[ ! -f /etc/yum.repos.d/nginx.repo ]]; then
cat > /etc/yum.repos.d/nginx.repo << 'NGINXREPO'
[nginx-stable]
name=nginx stable repo
baseurl=https://nginx.org/packages/rhel/9/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
NGINXREPO
fi
dnf remove -y nginx-core nginx-filesystem nginx-mod-* 2>/dev/null || true
dnf install -y nginx
systemctl enable nginx && systemctl start nginx
dnf install -y firewalld 2>/dev/null || true
firewall-cmd --zone=public --permanent --add-service=http 2>/dev/null || true
firewall-cmd --zone=public --permanent --add-service=https 2>/dev/null || true
firewall-cmd --reload 2>/dev/null || true
setsebool -P httpd_can_network_connect on 2>/dev/null || true
echo "Nginx 安装完成。"
fi
echo "[10/11] SSL(acme.sh)..."
if [[ "$INSTALL_SSL" == "yes" ]]; then
mkdir -p /etc/nginx/ssl
if [[ ! -x /root/.acme.sh/acme.sh ]]; then
curl -sSL https://get.acme.sh | sh -s "email=$GIT_EMAIL"
fi
export CF_Token CF_Account_ID CF_Key CF_Email
if [[ -n "$DOMAIN" ]] && { [[ -n "$CF_Token" ]] || [[ -n "$CF_Key" ]]; }; then
echo "申请证书:$DOMAIN 与 *.$DOMAIN ..."
/root/.acme.sh/acme.sh --issue --server letsencrypt --dns dns_cf -d "$DOMAIN" -d "*.$DOMAIN" --ecc
cp /root/.acme.sh/"${DOMAIN}"_ecc/{"$DOMAIN".cer,"$DOMAIN".key,fullchain.cer,ca.cer} /etc/nginx/ssl/
/root/.acme.sh/acme.sh --installcert -d "$DOMAIN" -d "*.$DOMAIN" --ecc \
--cert-file /etc/nginx/ssl/"$DOMAIN".cer \
--key-file /etc/nginx/ssl/"$DOMAIN".key \
--fullchain-file /etc/nginx/ssl/fullchain.cer \
--ca-file /etc/nginx/ssl/ca.cer \
--reloadcmd "nginx -s reload"
echo "证书已安装并配置自动续期。"
else
echo "未设置 DOMAIN 或 CloudFlare 凭证,仅完成 acme.sh 与 /etc/nginx/ssl。"
fi
fi
echo "[11/11] Docker ..."
if [[ "$INSTALL_DOCKER" == "yes" ]]; then
if ! command -v docker &>/dev/null; then
curl -fsSL https://get.docker.com | sh
fi
systemctl enable docker && systemctl start docker
iptables -P FORWARD ACCEPT 2>/dev/null || true
docker network create vps 2>/dev/null || true
echo "Docker 安装完成,已创建网络 vps(若不存在)。"
fi
echo "全部完成。"

变量说明

变量说明示例
HOSTNAME主机名,空则不设置vps
MIRROR软件源:空=默认,aliyunaws-bosaws-denaliyun
GIT_USER / GIT_EMAILGit 全局配置;邮箱同时用于 acme.sh 与 CloudFlare(CF_Email 未设时)-
CREATE_USER创建并加入 wheel 的用户名,空则不创建chensoul
SWAP_GBSwap 大小(GB),0 不配置2
SSH_KEY_ONLYyes=仅密钥登录,请先确认密钥可用no
PERMIT_ROOTprohibit-password / no / yesprohibit-password
INSTALL_FAIL2BANyes 安装 fail2banno
INSTALL_NGINXyes 安装 Nginx + 防火墙 + SELinuxyes
INSTALL_SSLyes 安装 acme.sh;有 DOMAIN+CF 凭证则申请证书yes
INSTALL_DOCKERyes 安装 Docker + 网络 vpsyes
DOMAIN主域名(通配符 *.$DOMAIN),空则不申请证书chensoul.cc
CF_TokenCloudFlare API Token(推荐)-
CF_Account_IDCloudFlare Account ID,与 CF_Token 配合使用-
CF_Key或使用 Global API Key(邮箱同 GIT_EMAIL,亦可设 CF_Email 覆盖)-

示例

cd /opt
git clone git@github.com:chensoul/vps-backup.git
cd vps-backup/scripts
# 仅系统设置 + Nginx + Docker,不申请证书
INSTALL_SSL=no bash almalinux9-setup.sh
# 系统设置 + 全部(含证书,需 CloudFlare 凭证)
MIRROR=aws-bos CREATE_USER=chensoul DOMAIN=chensoul.cc CF_Token="tEk7EtRfMHTC7kA9b_WwbJG-R4torcVzYhhBim3D" CF_Account_ID="2e1980a0ef43f57b920ea28cdf9d38d1" bash almalinux9-setup.sh

Docker 使用官方 get.docker.com;如需国内镜像或交互选源,可手动执行 bash <(curl -sSL https://linuxmirrors.cn/docker.sh) 后再运行本脚本(跳过 Docker 安装:INSTALL_DOCKER=no

服务部署脚本

前置:已将各服务的 postgres.yamlumami.yamlmemos.yamln8n.yamllinkding.yamlartalk.yaml 及(可选)uptime.yaml 放在同一目录,且 Nginx 配置已准备好(如放在该目录下 nginx/)。在该目录下执行:

set -e
DIR="${DIR:-$(pwd)}" # compose 文件所在目录,默认当前目录
cd "$DIR"
docker network create vps 2>/dev/null || true
docker compose -f postgres.yaml up -d
# 等 Postgres 就绪后创建数据库(非交互)
sleep 5
for db in umami memos n8n linkding artalk; do
docker exec postgres psql -U postgres -c "CREATE DATABASE $db OWNER postgres;" 2>/dev/null || true
done
docker compose -f umami.yaml up -d
docker compose -f memos.yaml up -d
docker compose -f n8n.yaml up -d
docker compose -f linkding.yaml up -d
docker compose -f artalk.yaml up -d
# 可选:docker compose -f uptime.yaml up -d
[ -d nginx ] && cp nginx/*.conf /etc/nginx/conf.d/ && nginx -t && nginx -s reload

定时任务与证书续期、备份等仍按上文「定时任务」一节单独配置即可。

参考文章

订阅文章

订阅更新,不错过后续文章

直接通过 RSS 和 Telegram 订阅本站更新。

订阅 RSS关注 Telegram

分享文章

如果这篇有帮助,可以顺手转发

直接分享给同事、朋友,或者发到你的社交平台。

分享到 X 分享到 Telegram 邮件分享
Git Flow 分支模型与使用
Mac 开发环境配置清单