我的VPS服务部署记录

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

我的 VPS 使用的是 CentOS 7 服务器,所以以下操作都是基于 CentOS 系统。

服务器设置

备份旧的 yum 源:

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

配置 CentOS 7 源:

curl -o /etc/yum.repos.d/Centos7-aliyun.repo https://mirrors.wlnmp.com/centos/Centos7-aliyun-x86_64.repo

清理缓存并重建缓存:

yum clean all
yum makecache

更新 yum 源:

yum update

安装常用软件:

yum install git vim jq ntp -y

设置时钟同步:

systemctl start ntpd
systemctl enable ntpd
ntpdate pool.ntp.org

设置时区为上海时区:

# 查看当前时区
timedatectl

# 设置时区为上海(Asia/Shanghai)
timedatectl set-timezone Asia/Shanghai

# 验证时区设置
timedatectl
date

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

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

# 1GB RAM with 2GB Swap
sudo fallocate -l 2G /swapfile && \
sudo dd if=/dev/zero of=/swapfile bs=1024 count=2097152 && \
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

安装 Nginx

参考 CentOS 7 下 yum 安装和配置 Nginx ,使用 yum 安装:

rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

yum install nginx -y

systemctl enable nginx

打开防火墙端口:

yum 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
  2. 创建 SSL 证书存储目录
# 创建 SSL 证书目录
mkdir -p /etc/nginx/ssl

# 安装 acme.sh
curl https://get.acme.sh | sh -s email=ichensoul@gmail.com

# 配置 CloudFlare API 凭证(使用 API Token 或 Global API Key)
export CF_Key="XXXXXXXXXXXXXXXXXX"  # CloudFlare Global API Key
export CF_Email="ichensoul@gmail.com"  # CloudFlare 账户邮箱

# 或者使用 API Token(推荐)
export CF_Token="your_api_token"
export CF_Account_ID="your_account_id"

# 申请证书(支持通配符域名)
~/.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 是内部预先创建的网络。

服务部署

Postgres

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

创建 postgres.yaml 文件:

services:
   postgres:
      image: postgres:18
      restart: always
      ports:
        - "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

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:
      - '5230:5230'
    networks:
      - vps

networks:
  vps:
    external: true

版本说明

  • 0.18.2 版本:功能丰富,包括电报机器人、webhook、S3 存储和自定义 CDN 域名等。支持多数据库(PostgreSQL、MySQL、SQLite)。
  • 0.14.4 版本:轻量级版本,无评论功能,仅支持 SQLite3。
  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;
    }
}
  1. 自定义样式(可选):

美化标签样式,在 Memos 设置中添加自定义 CSS:

.status-text{font-size:10px !important;border:none;color:rgb(156,163,175) !important;}
.tag-span,.dark .tag-span{border: 1px solid;border-radius:6px;padding:0px 6px;color:rgb(22,163,74) !important;font-size:12px !important;-webkit-transform: scale(calc(10 / 12));transform-origin: left center;}
.memo-content-text .link{color:rgb(22,163,74) !important;margin-right:-6px;}
header .bg-blue-600{display:none !important;}
.text-lg {font-size: 1rem !important;}
.header-wrapper,.sidebar-wrapper{width: 11rem !important;}
.filter-query-container{padding-bottom:0.5rem;}
  1. 重新加载 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:
      - '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 $host;
      proxy_http_version 1.1;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Upgrade $http_upgrade;     
      proxy_set_header X-Real-IP $remote_addr;
      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 参考:

Bark

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

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

  1. 在 iOS 上安装 Bark 应用

  2. 服务端使用 Docker 安装:

docker run -dt --name bark -p 8090:8080 -v /data/bark://data finab/bark-server
  1. 在 iOS Bark 应用上添加私有服务器:http://x.x.x.x:8090

  2. 测试推送功能:

# 替换 <device_key> 为你的设备密钥
curl -X "POST" "http://x.x.x.x:8090/<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"
}'
  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:8090;
        }
    }
    
  2. 重新加载 Nginx 配置:

nginx -t
nginx -s reload

Planka

Planka 是一个实时看板工具,使用 React 和 Redux 构建,可以作为 Trello 的开源替代品。

项目地址:https://github.com/plankanban/planka

安装步骤参考 官方文档

  1. 创建 planka.yaml 文件,配置使用已安装的 PostgreSQL 数据库:
services:
  planka:
    image: ghcr.io/plankanban/planka:latest
    container_name: planka
    restart: on-failure
    volumes:
      - /data/planka/user-avatars://app/public/user-avatars
      - /data/planka/project-background-images://app/public/project-background-images
      - /data/planka/attachments://app/private/attachments
    ports:
      - "1337:1337"
    environment:
      - BASE_URL=http://localhost:1337/
      - DATABASE_URL=postgresql://postgres:vps@027!@postgres:5432/planka
      - SECRET_KEY=notsecretkey

      # - TRUST_PROXY=0
      # - TOKEN_EXPIRES_IN=365 # In days

      # related: https://github.com/knex/knex/issues/2354
      # As knex does not pass query parameters from the connection string we
      # have to use environment variables in order to pass the desired values, e.g.
      # - PGSSLMODE=<value>

      # Configure knex to accept SSL certificates
      # - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false

      # - DEFAULT_ADMIN_EMAIL=demo@demo.demo # Do not remove if you want to prevent this user from being edited/deleted
      - DEFAULT_ADMIN_PASSWORD=demo
      - DEFAULT_ADMIN_NAME=Demo Demo
      - DEFAULT_ADMIN_USERNAME=demo

      # - SHOW_DETAILED_AUTH_ERRORS=false # Set to true to show more detailed authentication error messages. It should not be enabled without a rate limiter for security reasons.
      # - ALLOW_ALL_TO_CREATE_PROJECTS=true

      # - S3_ENDPOINT=
      # - S3_REGION=
      # - S3_ACCESS_KEY_ID=
      # - S3_SECRET_ACCESS_KEY=
      # - S3_BUCKET=
      # - S3_FORCE_PATH_STYLE=true

      # - OIDC_ISSUER=
      # - OIDC_CLIENT_ID=
      # - OIDC_CLIENT_SECRET=
      # - OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG=
      # - OIDC_USERINFO_SIGNED_RESPONSE_ALG=
      # - OIDC_SCOPES=openid email profile
      # - OIDC_RESPONSE_MODE=fragment
      # - OIDC_USE_DEFAULT_RESPONSE_MODE=true
      # - OIDC_ADMIN_ROLES=admin
      # - OIDC_CLAIMS_SOURCE=userinfo
      # - OIDC_EMAIL_ATTRIBUTE=email
      # - OIDC_NAME_ATTRIBUTE=name
      # - OIDC_USERNAME_ATTRIBUTE=preferred_username
      # - OIDC_ROLES_ATTRIBUTE=groups
      # - OIDC_IGNORE_USERNAME=true
      # - OIDC_IGNORE_ROLES=true
      # - OIDC_ENFORCED=true

      # Email Notifications (https://nodemailer.com/smtp/)
      # - SMTP_HOST=
      # - SMTP_PORT=587
      # - SMTP_NAME=
      # - SMTP_SECURE=true
      # - SMTP_USER=
      # - SMTP_PASSWORD=
      # - SMTP_FROM="Demo Demo" <demo@demo.demo>
      # - SMTP_TLS_REJECT_UNAUTHORIZED=false

      # Optional fields: accessToken, events, excludedEvents
      # - |
      #   WEBHOOKS=[{
      #     "url": "http://localhost:3001",
      #     "accessToken": "notaccesstoken",
      #     "events": ["cardCreate", "cardUpdate", "cardDelete"],
      #     "excludedEvents": ["notificationCreate", "notificationUpdate"]
      #   }]

      # - SLACK_BOT_TOKEN=
      # - SLACK_CHANNEL_ID=

      # - GOOGLE_CHAT_WEBHOOK_URL=

      # - TELEGRAM_BOT_TOKEN=
      # - TELEGRAM_CHAT_ID=
      # - TELEGRAM_THREAD_ID=
    networks:
      - vps

networks:
  vps:
    external: true
  1. 在 postgres 容器创建 planka 数据库:
docker exec -it postgres psql -U postgres
CREATE DATABASE planka OWNER postgres;
\q
  1. 启动服务:
docker compose -f planka.yaml up -d
docker logs -f planka
  1. 配置 nginx 配置文件 /etc/nginx/conf.d/planka.conf
server {
    listen 80;
    listen [::]:80;
    server_name planka.chensoul.cc;

    return 301 https://$host$request_uri;
}

server {
    listen          443 ssl;
    server_name     planka.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:1337;
    }
}
  1. 重新加载 Nginx 配置并访问服务:
nginx -t
nginx -s reload

访问 https://planka.chensoul.cc/,使用默认账号 demo/demo 登录(可在配置文件中修改)。

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:
      - "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 $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_set_header REMOTE-HOST $remote_addr;
        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。登录之后,请立即修改密码,并添加要统计的网站。

Kuma (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:
      - 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   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   Host $host;

        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

karakeep

karakeep 是一款可自托管的书签应用程序,带有 AI 智能功能,适合数据收集爱好者使用。

项目地址:https://github.com/karakeep-app/karakeep

  1. 通过 docker compose 安装,创建 karakeep.yaml
services:
  web:
    image: ghcr.io/karakeep-app/karakeep:${KARAKEEP_VERSION:-release}
    container_name: karakeep
    restart: unless-stopped
    volumes:
      - /data/karakeep:/data
    ports:
      - "3002:3000"
    environment:
      NEXTAUTH_SECRET: super_random_string
      MEILI_MASTER_KEY: another_random_string
      NEXTAUTH_URL: https://karakeep.chensoul.cc
      MEILI_ADDR: http://meilisearch:7700
      BROWSER_WEB_URL: http://chrome:9222
      # OPENAI_API_KEY: ...
      DATA_DIR: /data
  chrome:
    image: gcr.io/zenika-hub/alpine-chrome:124
    restart: unless-stopped
    command:
      - --no-sandbox
      - --disable-gpu
      - --disable-dev-shm-usage
      - --remote-debugging-address=0.0.0.0
      - --remote-debugging-port=9222
      - --hide-scrollbars
  meilisearch:
    image: getmeili/meilisearch:v1.13.3
    restart: unless-stopped
    environment:
      MEILI_NO_ANALYTICS: "true"
    volumes:
      - meilisearch:/meili_data
    networks:
      - vps

networks:
  vps:
    external: true
    
volumes:
  meilisearch:
  1. 启动服务:
docker compose -f karakeep.yaml up -d
docker logs -f karakeep
  1. 配置 nginx 配置文件 /etc/nginx/conf.d/karakeep.conf
server {
    listen 80;
    listen [::]:80;
    server_name karakeep.chensoul.cc;

    return 301 https://$host$request_uri;
}

server {
    listen          443 ssl;
    server_name     karakeep.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:3002;
        proxy_set_header Host $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_set_header REMOTE-HOST $remote_addr;
        add_header X-Cache $upstream_cache_status;
        add_header Cache-Control no-cache;
        expires 12h;
    }
}
  1. 重新加载 Nginx 配置:
nginx -t
nginx -s reload
  1. 使用 API 接口导入 URL(可选):

先创建 API Key,然后在 karakeep 设置中获取。

查询 memos 数据库(使用 PostgreSQL)中包含 URL 链接的记录:

SELECT (regexp_matches(content, 'https?://\S+', 'g'))[1] AS url FROM memo where content like '%https:%'

将查询结果保存为 all_links.txt

然后运行导入命令:

while IFS= read -r url; do
    docker run --rm ghcr.io/karakeep-app/karakeep-cli:release --api-key "ak1_066c79d8a9d634d4b826_d99f3a2732cab381f7c9" --server-addr "https://karakeep.chensoul.cc/" bookmarks add --link "$url"
done < all_links.txt

Blinko

Blinko 是一个AI驱动的卡片笔记项目,专为那些想要快速捕捉和组织灵感的人设计。它允许用户在灵感闪现的瞬间无缝记录想法,确保不错过任何创意火花。

创建 blinko.yaml 文件:

services:
  blinko:
    image: blinkospace/blinko:latest
    container_name: blinko
    environment:
      NODE_ENV: production
      NEXTAUTH_SECRET: my_ultra_secure_nextauth_secret
      DATABASE_URL: postgresql://postgres:vps@027!@postgres:5432/blinko
    restart: always
    ports:
      - "1111:1111"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://blinko:1111/"]
      interval: 30s 
      timeout: 10s   
      retries: 5     
      start_period: 30s 
    networks:
      - vps
      
networks:
  vps:
    external: true
  1. 创建数据库:
docker exec -it postgres psql -d postgres -U postgres
CREATE DATABASE blinko;
\q
  1. 启动服务:
docker compose -f blinko.yaml up -d
docker logs -f blinko
  1. 配置 nginx 配置文件 /etc/nginx/conf.d/blinko.conf
server {
   listen 80;
   listen [::]:80;
   server_name blinko.chensoul.cc;

   return 301 https://$host$request_uri;
}

server {
   listen          443 ssl;
   server_name     blinko.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:1111;
     proxy_set_header Host $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_set_header REMOTE-HOST $remote_addr;
     add_header X-Cache $upstream_cache_status;
     add_header Cache-Control no-cache;
     expires 12h;
   }
}
  1. 重新加载 Nginx 配置:
nginx -t
nginx -s reload

服务升级

使用 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

CONTAINER_NAME="postgres"
BACKUP_DIR="/opt/vps-backup/database"
BACKUP_PREFIX=postgres
BACKUP_POSTFIX=$(date +%Y%m%d_%H)
DATABASES=("memos" "n8n" "umami" "blinko")

mkdir -p $BACKUP_DIR

for DB_NAME in "${DATABASES[@]}"
do
  BACKUP_FILE="${BACKUP_PREFIX}_${DB_NAME}_${BACKUP_POSTFIX}.sql"

  echo "docker exec -it $CONTAINER_NAME pg_dump -U postgres $DB_NAME > $BACKUP_DIR/$BACKUP_FILE"
  docker exec -it $CONTAINER_NAME pg_dump -U postgres $DB_NAME > $BACKUP_DIR/$BACKUP_FILE

  if [ $? -eq 0 ]; then
    echo "DB $DB_NAME backup success: $BACKUP_FILE"
  else
    echo "DB $DB_NAME backup fail"
  fi
done

# 删除超过3天的备份文件
find $BACKUP_DIR -name "${BACKUP_PREFIX}_*.sql" -type f -mtime +3 -exec rm -f {} \;

cd /opt/vps-backup
git pull
git add .
git commit -m "backup database for $BACKUP_POSTFIX"
git push origin main

数据库恢复

恢复数据库备份:

# 恢复 memos 数据库
docker cp /opt/vps-backup/database/postgres_memos_20251111_12.sql postgres:/tmp/
docker exec -it postgres psql -U postgres -d memos -f /tmp/postgres_memos_20251111_12.sql

# 恢复 umami 数据库
docker cp /opt/vps-backup/database/postgres_umami_20251111_12.sql postgres:/tmp/
docker exec -it postgres psql -U postgres -d umami -f /tmp/postgres_umami_20251111_12.sql

N8n 工作流备份

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

#!/bin/bash

DATE=$(date +%Y%m%d)
BACKUP_DIR="/opt/vps-backup/n8n"
mkdir -p $BACKUP_DIR/$DATE

cd $BACKUP_DIR

docker exec -u node -it n8n n8n export:workflow --backup --output=./$DATE
docker cp n8n://home/node/$DATE ${BACKUP_DIR}

cd ${BACKUP_DIR}/$DATE
for file in `ls`; do
    filename=$(cat "$file" | jq -r '.name')  
    mv "$file" "$filename".json
done

docker exec -u node -it n8n n8n export:credentials --all --output=./credentials.json
docker cp n8n://home/node/credentials.json ${BACKUP_DIR}

# 删除超过3天的备份目录
find $BACKUP_DIR -type d -mtime +3 -exec rm -rf {} \;

cd /opt/vps-backup
git pull
git add .
git commit -m "backup n8n files for $DATE"
git push origin main

定时任务

使用 crontab 设置定时任务,实现自动化运维:

# 编辑 crontab
crontab -e

# 添加以下任务:
# SSL 证书自动续期(每天1点58分)
58 1 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

# 时钟同步(每20分钟)
*/20 * * * * /usr/sbin/ntpdate pool.ntp.org > /dev/null 2>&1

# 数据库备份(每3小时)
0 */3 * * * sh /opt/vps-backup/scripts/backup_database.sh

# N8n 工作流备份(每天凌晨2点)
0 2 * * * sh /opt/vps-backup/scripts/backup_n8n.sh

参考文章