Field Log · Entry

OpenClaw 구축: Docker Gateway와 Slack을 안전하게 연결하기 (5/7)

Slack Socket Mode가 인증된 연결로 Docker의 OpenClaw Gateway에 들어오고 agent별 workspace sandbox tool policy와 영속 state를 거치는 VPS 배포 구조

이번 글의 결과물

  • OpenClaw Gateway를 official image와 명시적 version으로 Docker에 배포합니다.
  • Config·workspace·provider auth·secret key를 container 교체 뒤에도 복구할 수 있게 영속화합니다.
  • Slack은 첫 배포에 public webhook이 필요 없는 Socket Mode로 연결합니다.
  • DM pairing, channel allowlist, mention gate, Gateway token, agent sandbox를 서로 다른 security layer로 구성합니다.
  • /healthz·/readyz, deep health, channel probe, restart test, rollback 기록이 모두 있어야 구축 완료로 봅니다.

앞 글에서는 Dashboard에서 한 agent와 읽기 전용 task를 검증했습니다. 이번에는 그 상태를 Linux VPS의 Docker Gateway와 Slack으로 옮깁니다.

이 글은 2026년 7월 21일, OpenClaw 2026.7.1 기준입니다. production에서는 아래 tag를 그대로 복사하기보다 배포 당일 공식 release와 image digest를 확인하세요.

1. 왜 첫 Slack 연결은 Socket Mode인가

OpenClaw의 Slack 문서는 Socket Mode와 HTTP Request URL을 모두 지원합니다.

조건Socket ModeHTTP Request URL
public Gateway URL불필요필요
연결 방향VPS → Slack outbound WSSSlack → public HTTPS
주요 secretbot token + app-level tokenbot token + signing secret
첫 단일 VPS단순reverse proxy·TLS 추가 필요
여러 replicaapp 연결 설계 필요load balancer 뒤 확장 용이

첫 구축은 단일 Gateway이므로 Socket Mode를 선택합니다. public ingress를 열지 않고도 Slack event를 받을 수 있어 network 변수가 줄어듭니다.

단, “public port가 없다”와 “인증이 안전하다”는 다른 말입니다. Slack token, sender allowlist, Gateway auth, tool permission은 별도로 설정합니다.

2. 배포 Contract

runtime:
  host: Linux VPS
  gateway: Docker Compose
  version: 2026.7.1  # 배포 시 실제 검증 tag로 교체
channel:
  provider: Slack
  transport: Socket Mode
  dm: pairing
  channels: allowlist
  require_mention: true
agent:
  count: 1
  workspace_access: read-only-first
  sandbox_scope: agent
network:
  public_gateway_port: false
  outbound_wss_to_slack: true
operations:
  liveness: /healthz
  readiness: /readyz
  backup: config + workspace + auth key

3. VPS와 Docker 준비

공식 Docker 문서는 image build에 최소 2GB RAM을 요구하며 1GB host에서는 dependency 설치가 OOM으로 끝날 수 있다고 경고합니다. OS·Docker Engine·Compose v2를 업데이트하고 disk 여유도 확인합니다.

docker --version
docker compose version
free -h
df -h

OpenClaw source를 가져옵니다.

git clone https://github.com/openclaw/openclaw.git
cd openclaw
git fetch --tags
git checkout <검증한-release-tag>

Production에서는 mutable latest보다 release tag 또는 image digest를 기록합니다. official registry는 GHCR가 primary입니다.

export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.7.1"
export OPENCLAW_HOME_VOLUME="openclaw_home"
export OPENCLAW_SANDBOX=1
./scripts/docker/setup.sh

배포 당일 tag가 존재하는지 registry에서 확인하고, 더 엄격한 환경에서는 digest로 고정합니다.

setup.sh는 onboarding, Gateway token 생성, Compose 시작을 도와줍니다. 자동화가 편해도 생성된 .env, compose override, mount를 직접 검토합니다.

4. 무엇이 영속화되는지 확인한다

Container는 교체 가능해야 하고 state는 남아야 합니다. 공식 Compose flow는 다음 종류의 data를 host path 또는 volume에 둡니다.

OpenClaw config/state
  openclaw.json
  agent별 SQLite session DB
  provider auth profile
  plugin install state

Agent workspace
  AGENTS.md · SOUL.md · USER.md · MEMORY.md
  memory/ · skills/

Auth profile secret key
  OAuth token material을 보호하는 local key

Container home (선택)
  외부 CLI binary와 그 auth를 container 안에 설치할 때 필요

Config directory와 auth-profile encryption key를 같은 의미로 보지 않습니다. 둘 중 하나만 복구하면 OAuth profile을 읽지 못할 수 있습니다. 반대로 workspace private Git backup에 auth directory를 섞어 push해서도 안 됩니다.

현재 mount를 확인합니다.

docker compose config
docker compose ps
docker volume ls

Secret 파일과 config permission도 확인합니다.

ls -la .env
chmod 600 .env

5. Gateway health부터 통과시킨다

Slack을 붙이기 전에 container 자체를 확인합니다.

curl -fsS http://127.0.0.1:18789/healthz
curl -fsS http://127.0.0.1:18789/readyz
docker compose ps
  • /healthz: process liveness
  • /readyz: 실제 요청을 받을 준비 상태

인증된 deep health도 확인합니다.

docker compose exec openclaw-gateway \
  node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"

VPS 외부 monitoring에는 agent chat endpoint가 아니라 전용 health endpoint를 사용합니다. /v1/chat/completions를 15분마다 ping하면 session과 model call이 생길 수 있습니다.

6. Gateway network를 공개하지 않는다

Docker setup은 host browser가 published port에 접근할 수 있도록 lan bind를 쓸 수 있습니다. 이것은 인터넷 전체에 노출해도 된다는 뜻이 아닙니다.

첫 배포 권장 경계:

VPS firewall: 18789 public deny
Gateway auth: generated token 유지
Operator access: SSH tunnel 또는 private network
Slack events: outbound Socket Mode

Dashboard가 필요하면 SSH tunnel을 사용합니다.

ssh -L 18789:127.0.0.1:18789 [email protected]

그 뒤 local browser에서 http://127.0.0.1:18789로 접속합니다. Tailscale을 쓴다면 OpenClaw의 Tailscale 문서에 따라 auth와 bind 조합을 별도로 검토합니다.

7. Slack plugin을 설치한다

현재 Slack connector는 plugin으로 설치합니다.

docker compose run --rm openclaw-cli \
  plugins install @openclaw/slack

설치 후 plugin 목록과 Gateway log에서 load 상태를 확인합니다.

docker compose run --rm openclaw-cli plugins list
docker compose logs --tail=200 openclaw-gateway

8. Slack App을 Socket Mode로 만든다

Slack channel 공식 문서의 current manifest를 사용해 app을 만듭니다. 문서의 scope에는 App Home·file·reaction까지 포함한 recommended와 더 작은 minimal variant가 있습니다. 첫 읽기 전용 bot이라면 필요 없는 scope를 제거한 variant를 선택합니다.

Slack admin 화면에서 다음을 수행합니다.

  1. api.slack.com/apps → Create New App → From a manifest
  2. Socket Mode가 켜진 manifest 적용
  3. App-Level Token 생성, scope는 connections:write
  4. App을 workspace에 설치
  5. Bot User OAuth Token 복사

Token 역할:

SLACK_APP_TOKEN  Socket Mode 연결 인증 (xapp-...)
SLACK_BOT_TOKEN  bot Web API 권한 (xoxb-...)

둘은 같은 Slack app·workspace에서 나온 값이어야 합니다. 다른 app의 token을 섞으면 연결이 실패합니다.

Token을 chat이나 openclaw.json plaintext에 붙이지 않고 container가 읽는 secret source에 넣습니다. 최소 예시는 Compose .env지만, 운영에서는 Docker secret·vault·platform secret manager를 선호합니다.

SLACK_APP_TOKEN=replace-with-secret
SLACK_BOT_TOKEN=replace-with-secret

shell history에 실제 token이 남는 export SLACK_BOT_TOKEN=xoxb-... 입력은 피합니다.

9. SecretRef와 allowlist를 config에 넣는다

다음은 구조를 보여 주는 JSON5 patch입니다. 실제 channel ID와 agent policy로 교체합니다.

{
  channels: {
    slack: {
      enabled: true,
      mode: "socket",
      appToken: {
        source: "env",
        provider: "default",
        id: "SLACK_APP_TOKEN",
      },
      botToken: {
        source: "env",
        provider: "default",
        id: "SLACK_BOT_TOKEN",
      },
      dmPolicy: "pairing",
      groupPolicy: "allowlist",
      channels: {
        C0123456789: {
          allow: true,
          requireMention: true,
        },
      },
    },
  },
}

Config는 dry-run 뒤 적용합니다.

docker compose run --rm openclaw-cli \
  config patch --file ./slack.socket.patch.json5 --dry-run

docker compose run --rm openclaw-cli \
  config patch --file ./slack.socket.patch.json5

Patch 파일에는 secret value가 아니라 SecretRef만 둡니다. Slack channel 이름 대신 안정적인 C... ID를 사용합니다.

10. Agent sandbox와 Tool policy를 분리한다

첫 Slack bot은 workspace를 읽기 전용으로 시작합니다.

{
  agents: {
    defaults: {
      sandbox: {
        mode: "all",
        scope: "agent",
        workspaceAccess: "ro",
      },
    },
  },
  tools: {
    deny: [
      "write",
      "edit",
      "apply_patch",
      "browser",
      "cron",
    ],
  },
}

각 layer의 역할은 다릅니다.

Layer막는 것
Slack allowlist누가 agent를 깨울 수 있는가
Tool denymodel이 어떤 capability를 호출할 수 있는가
Sandbox modetool process가 host에서 얼마나 격리되는가
Workspace accessagent 작업 파일을 ro/rw로 mount할 것인가
OS/container permissionkernel·filesystem 수준 실제 권한

Tool deny는 앞 단계에서 막힌 tool을 아래 단계가 다시 허용하지 못하는 축소 정책입니다. “Skill에서 write하라고 했으니 write tool이 생긴다”는 식으로 권한이 상승하지 않습니다.

Docker Gateway에서 agent sandbox까지 쓰는 경우 host Docker socket을 agent sandbox container에 mount하지 않습니다. 그러면 sandbox가 host container를 제어하는 통로가 됩니다.

11. Gateway를 재시작하고 Slack을 Probe한다

docker compose restart openclaw-gateway

docker compose run --rm openclaw-cli \
  channels status --probe

docker compose logs --tail=200 openclaw-gateway

정상일 때 허용 channel에서 bot을 mention합니다.

@OpenClaw 현재 agent id, workspace, model, 사용 가능한 tool만 알려줘.
아무 파일도 수정하지 마.

그다음 읽기 전용 smoke task를 보냅니다.

@OpenClaw package.json과 src/content.config.ts만 읽고
build command와 frontmatter 필수 필드를 표로 정리해줘.
각 결론에 파일 경로를 붙여줘.

12. Pairing과 비인가 경로를 시험한다

Slack DM 기본 pairing 요청을 확인합니다.

docker compose run --rm openclaw-cli pairing list slack

검증할 negative case:

  • allowlist 밖 channel에서 mention → 실행되지 않음
  • 허용 channel에서 mention 없음 → 실행되지 않음
  • 승인되지 않은 DM → pairing 전 tool 실행 안 됨
  • write 요청 → read-only workspace 또는 tool deny에서 차단
  • 잘못된 app token → channel probe가 명확히 실패

성공 case만 시험하면 가장 중요한 authorization bug를 놓칩니다.

13. Backup과 Rollback

배포 기록에 다음을 남깁니다.

release: 2026.7.1
image: ghcr.io/openclaw/openclaw@sha256:actual-digest
source_commit: actual-sha
config_backup: encrypted-location
workspace_backup: private-repo-or-snapshot
auth_key_backup: encrypted-location
slack_app_id: recorded-id
smoke_test: passed-at
previous_image: previous-digest

업데이트 전 read-only lint를 실행합니다.

docker compose run --rm openclaw-cli \
  doctor --lint --all

새 image로 교체한 뒤 같은 volume을 mount하면 startup migration이 수행될 수 있습니다. 실패할 때 state volume을 지우지 말고, 같은 mount로 openclaw doctor --fix를 한 번 실행하는 공식 복구 경로를 검토합니다. Rollback은 이전 image digest로 돌아가는 것뿐 아니라, 새 version이 state schema를 바꿨는지 release note와 backup에서 확인하는 일까지 포함합니다.

14. 구축 완료 Checklist

  • Source tag·image digest·배포 일자를 기록했다.
  • Config, workspace, provider auth, auth key가 영속화됐다.
  • Gateway port는 public internet에 직접 열리지 않았다.
  • /healthz, /readyz, authenticated health가 통과한다.
  • Slack app token과 bot token이 SecretRef로 해석된다.
  • DM은 pairing, channel은 allowlist, mention은 required다.
  • Agent sandbox scope와 workspace access가 명시돼 있다.
  • 허용·거부 case를 모두 시험했다.
  • Container·VPS 재시작 뒤 session과 channel이 복구된다.
  • 이전 image와 config/state backup으로 rollback 가능하다.

마무리

OpenClaw 구축은 Slack app을 연결하는 작업으로 끝나지 않습니다.

Slack sender
→ channel policy
→ Gateway auth와 routing
→ agent tool policy
→ sandbox와 workspace mount
→ 외부 system credential

이 chain의 어느 한 곳이라도 열려 있으면 “허용된 사람이 허용된 작업만 한다”는 보장이 깨집니다.

다음 글에서는 제가 실제로 봇을 약 20개까지 늘렸다가 한 project와 반복 workflow로 줄인 기록을 현재 OpenClaw 구조에 맞춰 다시 정리합니다. 구축 후 최적화의 첫 단계가 왜 model tuning이 아니라 봇과 업무 범위를 줄이는 일이었는지 다룹니다.

참고 자료