본문으로 건너뛰기

模組 10: 正式環境배포

학습 목표

이 모듈을 완료하면 다음을 할 수 있습니다:

  • 選擇並설정適合 OpenClaw 的 VPS 主機
  • 使用 Docker 或 Podman 進行컨테이너化배포
  • 使用 Nix 實現可重現的建構環境
  • 생성 systemd 服務確保 Agent 持續運行
  • 설정完善的모니터링、로그與백업策略
  • 完成從開發到正式環境的完整배포流程

핵심 개념

배포方式比較

배포方式優點缺點適用場景
直接설치簡單、低開銷環境污染、難以重現開發테스트
Docker生態系成熟、映像豐富需要 root daemon一般배포
PodmanRootless、無 daemon、安全生態系較小安全敏感環境
Nix完全可重現、宣告式學習曲線高進階배포、CI/CD
systemd原生 Linux 服務管理Linux 限定搭配上述任一方案

硬體需求

規模CPURAM저장說明
最小可用2 vCPU4 GB20 GB SSD單 Agent、無브라우저
建議4 vCPU8 GB50 GB SSD單 Agent、含브라우저
多 Agent8 vCPU16 GB100 GB SSD2-3 Agent、含브라우저
엔터프라이즈級16+ vCPU32+ GB200+ GB NVMe多 Agent + 모니터링 + 로그

推薦 VPS 服務商

服務商最低方案月費(約)備註
HetznerCX22 (2vCPU/4GB)~$4.5歐洲機房,CP 值最高
DigitalOceanBasic (2vCPU/4GB)~$24簡單易用
Linode/AkamaiNanode (1vCPU/2GB)~$5最低入門
VultrCloud Compute~$6全球節點多
AWS EC2t3.medium~$30엔터프라이즈級需求

실습 튜토리얼:VPS + Podman 배포

步驟一:VPS 初始설정

# 以 Hetzner CX22 為例,連線至 VPS
ssh root@YOUR_VPS_IP

# 생성非 root 사용자
adduser openclaw
usermod -aG sudo openclaw

# 설정 SSH Key 登入(更安全)
mkdir -p /home/openclaw/.ssh
cp ~/.ssh/authorized_keys /home/openclaw/.ssh/
chown -R openclaw:openclaw /home/openclaw/.ssh
chmod 700 /home/openclaw/.ssh
chmod 600 /home/openclaw/.ssh/authorized_keys

# 停用비밀번호登入
sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd

# 설정防火牆
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw enable

# ⚠️ 不要開放 18789 port!
# 如需遠端存取,使用 SSH tunnel
절대 port 18789를 개방하지 마세요

重溫 模組 9: 安全性 的教訓:全球有超過 135,000 個 OpenClaw 實例因為暴露 port 18789 而被入侵。永遠使用 SSH tunnel 來遠端存取。

# 從本機생성 SSH tunnel
ssh -L 18789:127.0.0.1:18789 openclaw@YOUR_VPS_IP
# 然後在本機使用 http://127.0.0.1:18789 存取

步驟二:설치 Podman

# 切換至 openclaw 사용자
su - openclaw

# 설치 Podman(Ubuntu/Debian)
sudo apt-get update
sudo apt-get install -y podman slirp4netns fuse-overlayfs

# 確認 Podman 版本
podman --version

# 確認 rootless 模式可用
podman info | grep -i rootless

# 설정 subuid/subgid(rootless 需要)
sudo usermod --add-subuids 100000-165535 openclaw
sudo usermod --add-subgids 100000-165535 openclaw

步驟三:準備설정 파일

# 생성目錄結構
mkdir -p ~/openclaw/{config,data,logs,skills,tls}

생성 settings.json

{
"server": {
"host": "127.0.0.1",
"port": 18789,
"auth": {
"enabled": true,
"api_key": "${OPENCLAW_API_KEY}"
}
},
"llm": {
"provider": "openai",
"model": "gpt-4o",
"api_key": "${OPENAI_API_KEY}",
"max_tokens": 4096,
"temperature": 0.7
},
"channels": {
"discord": {
"enabled": true,
"token": "${DISCORD_BOT_TOKEN}",
"guild_id": "${DISCORD_GUILD_ID}"
}
},
"browser": {
"enabled": true,
"headless": true,
"launch_options": {
"args": ["--no-sandbox", "--disable-dev-shm-usage"]
}
},
"logging": {
"level": "info",
"file": "/data/logs/openclaw.log",
"max_size_mb": 100,
"max_files": 10,
"rotation": "daily"
},
"data_dir": "/data"
}

생성環境變數檔案:

# ~/openclaw/.env(確保권한為 600)
cat > ~/openclaw/.env << 'EOF'
OPENCLAW_API_KEY=your_api_key_here
OPENAI_API_KEY=sk-your-openai-key
DISCORD_BOT_TOKEN=your-discord-bot-token
DISCORD_GUILD_ID=your-guild-id
EOF

chmod 600 ~/openclaw/.env

步驟四:使用 Podman 啟動

# 拉取 OpenClaw 映像
podman pull ghcr.io/openclaw/openclaw:latest

# 啟動컨테이너
podman run -d \
--name openclaw \
--userns=keep-id \
--security-opt=no-new-privileges \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--cap-add=SYS_ADMIN \
--read-only \
--tmpfs /tmp:rw,size=200m \
--tmpfs /run:rw,size=50m \
-p 127.0.0.1:18789:18789 \
-v ~/openclaw/config/settings.json:/app/settings.json:ro,Z \
-v ~/openclaw/config/soul.md:/app/soul.md:ro,Z \
-v ~/openclaw/data:/data:Z \
-v ~/openclaw/skills:/app/skills:ro,Z \
--env-file ~/openclaw/.env \
--memory=4g \
--cpus=2 \
--restart=unless-stopped \
ghcr.io/openclaw/openclaw:latest

# 確認컨테이너狀態
podman ps

# 조회로그
podman logs -f openclaw
SYS_ADMIN capability

--cap-add=SYS_ADMIN 是 Headless Chromium 在컨테이너中運行所需的。如果不需要브라우저功能,可以移除此 capability 以增強安全性。

步驟五:systemd 사용자服務

생성 systemd 服務讓 Podman 컨테이너在開機時自動啟動:

# 생성 systemd 사용자服務目錄
mkdir -p ~/.config/systemd/user/

# 從 Podman 컨테이너自動產生 systemd 服務
podman generate systemd --name openclaw --new --files
mv container-openclaw.service ~/.config/systemd/user/

# 或手動생성服務檔案
cat > ~/.config/systemd/user/openclaw.service << 'EOF'
[Unit]
Description=OpenClaw AI Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
Restart=always
RestartSec=10
TimeoutStartSec=60
TimeoutStopSec=30

ExecStartPre=-/usr/bin/podman rm -f openclaw
ExecStart=/usr/bin/podman run \
--name openclaw \
--userns=keep-id \
--security-opt=no-new-privileges \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--cap-add=SYS_ADMIN \
--read-only \
--tmpfs /tmp:rw,size=200m \
--tmpfs /run:rw,size=50m \
-p 127.0.0.1:18789:18789 \
-v %h/openclaw/config/settings.json:/app/settings.json:ro,Z \
-v %h/openclaw/config/soul.md:/app/soul.md:ro,Z \
-v %h/openclaw/data:/data:Z \
-v %h/openclaw/skills:/app/skills:ro,Z \
--env-file %h/openclaw/.env \
--memory=4g \
--cpus=2 \
ghcr.io/openclaw/openclaw:latest

ExecStop=/usr/bin/podman stop -t 30 openclaw
ExecStopPost=-/usr/bin/podman rm -f openclaw

[Install]
WantedBy=default.target
EOF

# 啟用並啟動服務
systemctl --user daemon-reload
systemctl --user enable openclaw.service
systemctl --user start openclaw.service

# 確保사용자 systemd 在未登入時仍運行
loginctl enable-linger openclaw

# 檢查狀態
systemctl --user status openclaw.service

步驟六:Nix 可重現배포(進階)

如果你偏好 Nix 的宣告式배포:

# flake.nix
{
description = "OpenClaw production deployment";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
openclaw.url = "github:openclaw/openclaw";
};

outputs = { self, nixpkgs, openclaw }: {
nixosConfigurations.openclaw-server = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hardware-configuration.nix
openclaw.nixosModules.default
({ config, pkgs, ... }: {
services.openclaw = {
enable = true;
settings = {
server.host = "127.0.0.1";
server.port = 18789;
llm.provider = "openai";
llm.model = "gpt-4o";
browser.enabled = true;
browser.headless = true;
};
environmentFile = "/run/secrets/openclaw.env";
};

# 防火牆
networking.firewall = {
enable = true;
allowedTCPPorts = [ 22 ];
# 不開放 18789
};

# 自動업데이트
system.autoUpgrade = {
enable = true;
flake = "github:openclaw/openclaw";
dates = "04:00";
};
})
];
};
};
}

배포:

# 建構並배포
nixos-rebuild switch --flake .#openclaw-server

# 或遠端배포
nixos-rebuild switch --flake .#openclaw-server \
--target-host openclaw@YOUR_VPS_IP

步驟七:모니터링與로그

로그聚合:

# 使用 journalctl 조회 systemd 服務로그
journalctl --user -u openclaw.service -f

# 搭配 loki + grafana 進行로그聚合
# docker-compose.monitoring.yml
# monitoring/docker-compose.yml
version: '3'
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "127.0.0.1:9090:9090"

grafana:
image: grafana/grafana:latest
ports:
- "127.0.0.1:3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_PASSWORD}"

健康檢查腳本:

#!/bin/bash
# ~/openclaw/scripts/health-check.sh

OPENCLAW_URL="http://127.0.0.1:18789/api/health"
ALERT_WEBHOOK="${DISCORD_WEBHOOK_URL}"

response=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${OPENCLAW_API_KEY}" \
"$OPENCLAW_URL")

if [ "$response" != "200" ]; then
# 嘗試重啟
systemctl --user restart openclaw.service

# 發送 Discord 通知
curl -X POST "$ALERT_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{
\"content\": \"⚠️ OpenClaw 健康檢查失敗 (HTTP $response),已嘗試重啟。\"
}"
fi
# 加入 crontab
chmod +x ~/openclaw/scripts/health-check.sh
crontab -e
# 加入:
# */5 * * * * /home/openclaw/openclaw/scripts/health-check.sh

步驟八:백업策略

#!/bin/bash
# ~/openclaw/scripts/backup.sh

BACKUP_DIR="/home/openclaw/backups"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

mkdir -p "$BACKUP_DIR"

# 백업데이터(排除임시 저장檔)
tar -czf "$BACKUP_DIR/openclaw-data-$DATE.tar.gz" \
-C /home/openclaw/openclaw \
--exclude='*.tmp' \
--exclude='logs/*.log.*' \
data/ config/ skills/

# 백업 soul.md
cp ~/openclaw/config/soul.md "$BACKUP_DIR/soul-$DATE.md"

# 清理舊백업
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR" -name "*.md" -mtime +$RETENTION_DAYS -delete

# 可選:업로드至遠端저장
# rclone copy "$BACKUP_DIR/openclaw-data-$DATE.tar.gz" remote:openclaw-backups/

echo "백업完成: openclaw-data-$DATE.tar.gz"
# 每日自動백업
crontab -e
# 加入:
# 0 3 * * * /home/openclaw/openclaw/scripts/backup.sh >> /home/openclaw/openclaw/logs/backup.log 2>&1

자주 발생하는 오류

問題原因解決方案
컨테이너啟動後立即退出環境變數未설정確認 .env 檔案內容和路徑
Chromium 無法啟動缺少 SYS_ADMIN capability加入 --cap-add=SYS_ADMIN
systemd 服務開機不啟動未啟用 linger실행 loginctl enable-linger
磁碟空間不足로그未輪替설정 max_size_mbmax_files
컨테이너內無法解析 DNSPodman 網路설정問題加入 --dns=8.8.8.8
SELinux 阻擋 volume 掛載SELinux 標籤不符使用 :Z 標記掛載 volume

문제 해결

# 컨테이너無法啟動 — 조회詳細로그
podman logs openclaw 2>&1 | tail -50

# 網路問題
podman exec openclaw curl -s http://127.0.0.1:18789/api/health

# 檔案권한問題
podman exec openclaw ls -la /data/
podman unshare ls -la ~/openclaw/data/

# systemd 服務失敗
systemctl --user status openclaw.service
journalctl --user -u openclaw.service --no-pager -n 50

# 資源使用狀況
podman stats openclaw --no-stream

연습 문제

연습 1:基礎 VPS 배포

在一台 VPS 上使用 Podman 배포 OpenClaw,確保:

  • 只監聽 127.0.0.1
  • 使用 systemd 管理
  • 설정健康檢查腳本

연습 2:完整모니터링

為練習 1 的배포加入 Prometheus + Grafana 모니터링,생성以下 Dashboard:

  • Agent 回應時間
  • LLM API 使用量
  • 記憶體 / CPU 使用率
  • 錯誤率

연습 3:災難復原

模擬以下場景並制定復原計畫:

  • VPS 硬碟故障
  • LLM API Key 外洩
  • Agent 被注入惡意 Prompt

퀴즈

  1. 為什麼建議使用 loginctl enable-linger

    • A) 提升성능
    • B) 讓사용자的 systemd 服務在未登入時仍持續運行
    • C) 啟用 root 권한
    • D) 自動업데이트系統
    정답 확인
    B) 預設情況下,사용자的 systemd 服務會在사용자登出後停止。enable-linger 確保服務在사용자未登入時仍然運行。
  2. Podman 的 --read-only 參數搭配 --tmpfs 的目的是什麼?

    • A) 提升磁碟성능
    • B) 唯讀檔案系統防止被寫入惡意檔案,tmpfs 提供必要的임시 저장空間
    • C) 節省磁碟空間
    • D) 암호화檔案系統
    정답 확인
    B) --read-only 確保공격자無法在컨테이너中寫入後門或惡意程式。--tmpfs 為需要寫入的 /tmp 等目錄提供記憶體임시 저장空間。
  3. 로그輪替中 max_files: 10 搭配 max_size_mb: 100 代表什麼?

    • A) 最多保留 10 個檔案,每個最大 100MB,即最多約 1GB 로그
    • B) 總共 10MB 的로그
    • C) 永遠保留所有로그
    • D) 每 10 分鐘產生新檔案
    정답 확인
    A) 當로그檔案達到 100MB 時會自動輪替,最多保留 10 個歷史檔案,因此로그最多佔用約 1GB 磁碟空間。
  4. 以下哪項不是必要的백업項目?

    • A) data/ 目錄(Agent 記憶與데이터)
    • B) settings.json
    • C) soul.md
    • D) Podman 映像캐시
    정답 확인
    D) Podman 映像可以隨時從 registry 重新拉取,不需要백업。重要的是 Agent 的데이터、설정和 soul.md。

다음 단계