Search for a command to run...
저는 로컬 개발 환경인 macOS에서 Docker를 native하게 실행시키지 못한다는 이유로 홈 서버(Ubuntu)에 Docker를 올려 원격으로 사용하고 있습니다. macOS 환경 유저분들은 저와 비슷한 경우가 많을 거라고 생각합니다. 따라서, 이번 아티클은 제가 원격으로 Docker를 사용하기 위해 어떤 도구와 방법을 사용했는 지와 간단한 사용법을 소개하겠습니다.
저는 Docker 관리 시, lazydocker를 애용합니다. 이는 Docker 및 Docker Compose 관리 TUI 툴이며, Dockge 또는 Portainer와 같은 WebUI 기반 무거운 관리 툴을 사용하지 못하는 환경에서 사용하기 좋습니다.

단, 다음과 같은 이유로 제 환경에서 lazydocker를 사용하려면, Docker를 로컬 시스템에서 접근할 수 있어야 했습니다.
SSH 주소 기반 접근을 시도해봤지만, 비밀번호 없이 SSH 키를 사용하는 접근 방식은 지원하지 않았습니다. 관련 이슈에 대한 기능 추가 PR이 존재하고 문제가 해결된 포크 레포지토리도 있는 듯 보이지만, 어째서인지 PR Merge가 이루어지지 않고 있습니다 :(
따라서, 조금 귀찮지만 재밌는 접근 방법을 사용해 보겠습니다.
먼저 Docker가 사용하는 통신 방법인 Unix Domain Socket에 대해 알아보겠습니다.
일반적인 TCP/IP 소켓 통신 같은 경우에는 네트워크 통신을 사용하기에, ip:port 기반의 주소 시스템을 사용합니다. 하지만 로컬 시스템에서만 통신하다면, 굳이 네트워크 스택을 탈 필요가 없으며, 이런 경우를 위해 Unix 시스템에서는 Unix Domain Socket을 사용할 수 있습니다.
Unix Domain Socket은 네트워크 스택을 타지 않기에 목적지 주소로 ip:port 대신 .sock 파일의 경로를 사용합니다.
$ ls -l docker.sock
srw-rw---- 1 root docker 0 Jan 1 00:00 docker.sock
파일 타입은 s(socket)이며, 파일 시스템 접근 권한으로 소켓에 대한 접근 권한 제어가 가능합니다. 다음은 이 docker.sock 파일을 외부에서 접근하는 방법에 대해서 알아보겠습니다.
SSH는 로컬 포워딩이라는 기능을 제공하며, 이 기능은 SSH 터널을 통한 스트림 포워딩 기능입니다. 단순히 설명하자면, SSH 터널을 사용하는 TCP 포트나 Unix Domain Socket 포워딩 기능입니다. 이를 활용해 원격 서버의 TCP 포트를 활용하는 서비스나 Unix Domain Socket을 로컬에서 실행 중인 것처럼 보이게 만들 수 있습니다.
$ ssh -N -L /tmp/docker.sock:/var/run/docker.sock myuser@myhost
SSH의 -L 옵션을 통해 로컬 포워딩이 가능하며, -N 옵션은 원격 명령을 실행하지 않고(쉘 없이) 터널 연결만 유지한다는 의미입니다. 이를 통해 로컬의 /tmp/docker.sock에 원격 서버의 /var/run/docker.sock을 포워딩할 수 있습니다.
하지만, 매번 로컬에서 원격 Docker에 접근할 때 마다, 명령어 입력은 번거롭기에, ~/.ssh/config 파일을 편집해 이를 선언화 해보겠습니다.
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
...
위 설정은 SSH 멀티플렉싱을 활성화합니다.
SSH 멀티플렉싱이란 하나의 TCP 연결에서 여러 개의 SSH 세션을 공유하는 기술입니다.
ControlMaster auto : 첫 접속 시 마스터 연결을 생성하고, 이후 접속은 이를 공유ControlPath : 공유 연결을 관리하기 위한 소켓 파일 경로ControlPersist 10m : 마지막 터널 사용이 끝난 후, 10분 간 백그라운드에서 유지 (재접속 속도 향상)...
Host myhost
HostName x.x.x.x
Port 22
User myuser
IdentityFile ~/.ssh/id_ed25519
LocalForward /tmp/docker.sock /var/run/docker.sock
StreamLocalBindUnlink yes
ExitOnForwardFailure yes
LocalForward [local] [remote] : 로컬의 [local]에 들어오는 요청은 원격의 [remote]로 전달
StreamLocalBindUnlink yes : 로컬에 소켓 파일이 이미 존재하는 경우, 덮어쓰기ExitOnForwardFailure yes : 포워딩에 실패할 경우, SSH 접속 종료 (오류 출력)접근에 사용될 사용자는
docker그룹에 속해 있거나, root 계정이어야 합니다.
$ ssh -fN myhost
설정이 완료되었다면, 위 명령어를 통해 SSH 터널을 생성합니다. (-f 옵션을 통해 백그라운드에서 실행)
$ ssh -O check myhost # ControlMaster가 살아 있는 지
$ ssh -O exit myhost # ControlMaster 즉시 종료
이후 SSH -O 옵션을 통해, SSH 멀티플렉싱 마스터(ControlMaster)에게 명령할 수 있습니다.
lazydocker는 1차적으로 DOCKER_HOST 환경변수를 통해 docker.sock을 찾습니다. 따라서 방금 로컬에 포워딩한 docker.sock의 경로를 설정해줘야 합니다.
export DOCKER_HOST=unix:///tmp/docker.sock
이때 Unix Domain Socket을 알리기 위해 unix:// 스키마 접두사를 사용합니다.
이제 모든 설정은 완료되었으며, 외부 시스템에 설치된 Docker를 lazydocker로 접근할 수 있습니다!
최근 웹 서핑 중, Tori 라는 프로젝트를 발견했습니다.

Tori는 Go 언어로 작성된 경량 Docker 서버 모니터링 TUI 툴이며 다음과 같은 기능을 제공합니다:
현재(2026년 2월) 기준, 개발 및 테스트 단계이며 기존 모니터링 도구들을 완전히 대체하지 못합니다!
Tori는 다음과 같은 구성 요소를 가집니다:
tori.sock에 접근하기 위한 클라이언트
tori agent의 설치를 위해 Docker Compose를 사용해 보겠습니다.
services:
tori-agent:
container_name: tori-agent
image: ghcr.io/thobiasn/tori-cli:latest
restart: unless-stopped
pid: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /run/tori:/run/tori
- ./config.toml:/etc/tori/config.toml
- ./data:/var/lib/tori
볼륨 설정을 통해, 다음을 설정합니다:
/proc, /sys 접근 허용docker.sock에 대한 접근 허용tori.sock 관리를 위한 /run/tori 경로 마운트config.toml) 및 SQLite 데이터 마운트[storage]
path = "/var/lib/tori/tori.db"
retention_days = 7
[socket]
path = "/run/tori/tori.sock"
[host]
proc = "/host/proc"
sys = "/host/sys"
[docker]
socket = "/var/run/docker.sock"
[collect]
interval = "10s"
[alerts.container_unhealthy]
condition = "container.health == 'unhealthy'"
for = "30s"
severity = "critical"
actions = ["notify"]
[alerts.container_down]
condition = "container.state == 'exited'"
for = "0s"
severity = "critical"
actions = ["notify"]
[alerts.container_restart_loop]
condition = "container.restart_count > 5"
severity = "critical"
actions = ["notify"]
[alerts.host_high_cpu]
condition = "host.cpu_percent > 90"
for = "1m"
severity = "warning"
actions = ["notify"]
[alerts.host_high_memory]
condition = "host.memory_percent > 90"
for = "1m"
severity = "warning"
actions = ["notify"]
[alerts.host_high_swap]
condition = "host.swap_percent > 85"
for = "30s"
severity = "warning"
actions = ["notify"]
[alerts.host_disk_full]
condition = "host.disk_percent > 90"
for = "0s"
severity = "critical"
actions = ["notify"]
[alert] 설정은 특정 상황에서 경고를 남기는 룰을 지정하며, 위 예제는 개인적으로 사용 중인 룰입니다. 이는 사용하시는 환경에 맞게 따로 설정해주시면 됩니다.
[[notify.webhooks]]
enabled = true
url = "https://discord.com/api/webhooks/..."
template = '{"content": "**{{.Subject}}**\n{{.Body}}"}'
[notify] 설정을 통해 알림 시스템을 설정할 수 있으며, 개인적으로 Discord 알림화를 자주 사용하기에, Discord 웹훅을 등록했습니다. SMTP를 통한 이메일 알림화도 가능합니다.
모든 설정이 끝나면 docker compose up을 통해 tori agent를 실행해주세요.
tori client란 방금 설치한 tori agent에 접속할 TUI 클라이언트이며, 다음과 같은 방법으로 설치가 가능합니다:
$ curl -fsSL https://raw.githubusercontent.com/thobiasn/tori-cli/main/deploy/install.sh | sh -s -- --client
설치가 끝났다면, tori myuser@myhost 또는 ~/.config/tori/config.toml 파일에 서버를 설정해줄 수 있습니다.
이외에도 Docker 이미지 탐색 툴인 dive, 원격 서버에 Docker 이미지를 전달하는 unregistry 등 다양한 도구들이 있지만, 이번 아티클에선 접근과 관리 그리고 모니터링을 중점으로 다뤄봤습니다.
단, 이 아티클에서는 docker.sock을 로컬로 포워딩하고 있으며, 이는 원격 서버의 Docker를 제어할 수 있는 권한을 부여하는 것이며, Docker 특성상 권한 상승에 매우 취약합니다. 예를 들어, 모든 파일 시스템을 마운트하여 접근할 수 있으며, 이는 사실상 root 권한을 부여하는 것과 같습니다.
만약 비슷한 환경을 구축하고자 하는 경우, 꼭 신뢰할 수 있는 환경에서만 세팅하는 것을 권장합니다.
로그인 후 댓글을 작성할 수 있습니다.