Raspberry Pi 4 PXVIRT(Proxmox VE) 설치 가이드
실제 설치 과정에서 겪은 문제와 해결 과정을 그대로 반영한 문서입니다. 환경: Raspberry Pi 4, Raspberry Pi OS Lite 64-bit (Trixie / Debian 13 기반), 유선 이더넷 + WiFi 병행
목차
- 사전 준비 사항
- 프로젝트 배경 – 왜 PXVIRT인가
- 저장소 및 GPG 키 설정
- 네트워크 설정
- DNS 설정
- 호스트명 해석 문제와 cloud-init
- PXVIRT 패키지 설치
- 웹 UI 접속 및 리버스 프록시(NPM) 연동
- pveam 앱 템플릿 카탈로그 서명 오류와 arm64 LXC 템플릿 확보
- Zigbee2MQTT 컨테이너 구축 (USB 패스스루 + Docker) (10.5: Dockge 설치 및 Zigbee2MQTT 배포, 10.8: 버전 업그레이드, 10.9: 고정 IP 전환, 10.10: root 콘솔 로그인 잠금, 10.11: IPv6 link-local 미생성, 10.12: Matterbridge 추가)
- 최종 설정 파일 모음
- 트러블슈팅 요약표
- 재부팅 후 검증 체크리스트
1. 사전 준비 사항
- Raspberry Pi 4 (RAM 4GB 이상 권장)
- Raspberry Pi OS Lite 64-bit (Bookworm 또는 Trixie)
- Bookworm → Proxmox 8 계열 설치됨, 커뮤니티 검증 많음 (권장)
- Trixie → Proxmox 9 계열 설치됨, 최신이지만 LXC 네트워킹 등 일부 이슈 보고됨
- 유선 이더넷 연결 필수 (WiFi만으로는 VM/LXC용 네트워크 브릿지 구성이 어려움)
- SSH 접속 가능한 상태
- 라우터에서 고정 IP(또는 DHCP 예약) 확보
⚠️ 중요: Proxmox 공식은 ARM/라즈베리파이를 지원하지 않습니다. 여기서 사용하는 PXVIRT는 커뮤니티 비공식 포크이며, 프로덕션보다는 홈랩/테스트 용도에 적합합니다.
2. 프로젝트 배경 – 왜 PXVIRT인가
기존에 널리 쓰이던 mirrors.apqa.cn/proxmox/debian/pve (프로젝트명 “Proxmox-Port”, pveport)는 상표권 문제로 폐지되었고, 후속 프로젝트인 PXVIRT로 완전히 이전되었습니다. 기존 저장소는 서버 자체가 죽어서(521 에러, Cloudflare가 origin에 연결 실패) 더 이상 사용할 수 없습니다.
| 항목 | 기존 (pveport, 폐지됨) | 현재 (PXVIRT) |
|---|---|---|
| URL | mirrors.apqa.cn/proxmox/debian/pve | mirrors.lierfang.com/pxcloud/pxvirt |
| 상태 | 서버 다운, 더 이상 미유지보수 | 유지보수 중 |
| 지원 코드네임 | bullseye/bookworm | bookworm, trixie |
| 패키지 | proxmox-ve 단일 | proxmox-ve, pve-manager, qemu-server, pve-cluster 등 |
3. 저장소 및 GPG 키 설정
기존 pveport 관련 파일이 있다면 먼저 제거합니다.
sudo rm -f /etc/apt/sources.list.d/pveport.list
sudo rm -f /usr/share/keyrings/pveport.gpg
PXVIRT GPG 키를 받고 저장소를 등록합니다.
sudo curl -L https://mirrors.lierfang.com/pxcloud/lierfang.gpg -o /etc/apt/trusted.gpg.d/lierfang.gpg
source /etc/os-release
echo "deb https://mirrors.lierfang.com/pxcloud/pxvirt $VERSION_CODENAME main" | \
sudo tee /etc/apt/sources.list.d/pxvirt-sources.list
저장소 갱신으로 서명 검증까지 정상 통과하는지 확인합니다.
sudo apt update
💡
Notice: Skipping acquire of ... armhf ...경고는 무시해도 됩니다. PXVIRT가 arm64만 지원하기 때문에 뜨는 정상 안내입니다.
4. 네트워크 설정
4.1 문제 상황
Raspberry Pi OS(Bookworm/Trixie)는 기본적으로 NetworkManager가 모든 인터페이스(이더넷, WiFi)를 관리합니다. Proxmox의 VM/LXC 브릿지(vmbr0)는 보통 ifupdown2 방식의 정적 설정을 필요로 하기 때문에 역할 분리가 필요합니다.
⚠️ 겪었던 실패: NetworkManager를 무작정 systemctl disable --now로 꺼버리면, ifupdown2 쪽에 아직 아무 설정이 없는 상태에서 인터페이스를 아무도 관리하지 않게 되어 네트워크가 완전히 끊기고 SSH 접속이 불가능해졌습니다. 물리 접근이나 SD카드를 다른 PC에 마운트해서 복구해야 했습니다.
4.2 안전한 해결 방식
핵심 전략: NetworkManager를 통째로 끄지 않고, eth0만 “unmanaged”로 지정해서 ifupdown2가 전담하게 하고, WiFi(wlan0)는 NetworkManager가 계속 관리하도록 역할을 분리합니다. 이러면 유선 설정에 문제가 생겨도 WiFi로 접속할 수 있는 안전망이 남습니다.
1단계: ifupdown2 설치
sudo apt install -y ifupdown2
2단계: eth0에 정적 IP 설정 (/etc/network/interfaces)
sudo tee -a /etc/network/interfaces > /dev/null <<EOF
auto eth0
iface eth0 inet static
address 192.168.219.128/24
gateway 192.168.219.1
dns-nameservers 192.168.219.130
EOF
3단계: NetworkManager가 eth0을 건드리지 않도록 설정
sudo mkdir -p /etc/NetworkManager/conf.d
sudo tee /etc/NetworkManager/conf.d/unmanaged-eth0.conf > /dev/null <<EOF
[keyfile]
unmanaged-devices=interface-name:eth0 EOF sudo systemctl restart NetworkManager
4단계: eth0 인터페이스 올리기
sudo ifup eth0
ip addr show eth0
5단계: 최종 확인
nmcli device status
정상 결과 예시:
DEVICE TYPE STATE CONNECTION
wlan0 wifi connected ...
eth0 ethernet unmanaged --
⚠️ 주의: 만약 이전에 NetworkManager를
disable했었다면 재부팅 후 자동으로 켜지지 않습니다. 아래 명령으로 반드시 다시 활성화하세요.sudo systemctl enable --now NetworkManager
5. DNS 설정
5.1 문제 상황
/etc/network/interfaces에 dns-nameservers를 적어도, resolvconf 패키지가 없으면 ifupdown2가 이 값을 /etc/resolv.conf에 실제로 반영하지 않습니다. 그 결과 apt update 시 Temporary failure resolving ... 에러가 발생했습니다.
또한 /etc/resolv.conf가 NetworkManager가 관리하는 심볼릭 링크인 경우, eth0을 unmanaged로 뺀 직후에는 NM이 DNS 정보를 못 가져와 빈 파일만 생성하는 문제도 있었습니다.
5.2 해결
1) 임시 조치 (resolvconf 설치 전, apt update가 되게 하기 위한 응급처치)
echo "nameserver <실제 DNS 서버 IP>" | sudo tee /etc/resolv.conf
게이트웨이 IP가 곧 DNS 서버라고 단정하지 말고,
ip route show로 실제 게이트웨이를 확인한 뒤 라우터가 DNS 프록시 역할을 하는지 확인하세요. 안 되면1.1.1.1,8.8.8.8같은 공용 DNS를 사용합니다.
2) 영구 조치: resolvconf 설치
sudo apt update
sudo apt install -y resolvconf
3) /etc/network/interfaces의 DNS 값을 실제 서버로 수정
sudo sed -i 's/dns-nameservers .*/dns-nameservers 192.168.219.130/' /etc/network/interfaces
sudo ifdown eth0 && sudo ifup eth0
cat /etc/resolv.conf
resolvconf(8)가 생성한 파일이라는 주석과 함께 nameserver가 유지되면 성공입니다.
6. 호스트명 해석 문제와 cloud-init
6.1 증상
pve-cluster.service(pmxcfs)가 계속 재시작에 실패:
pmxcfs[...]: [main] crit: Unable to resolve node name 'raspberrypi' to a non-loopback IP address - missing entry in '/etc/hosts' or DNS?
Proxmox는 노드 이름이 루프백이 아닌 실제 IP로 해석되어야 정상 기동합니다. 그런데 /etc/hosts에는 기본적으로:
127.0.1.1 raspberrypi
이렇게 루프백으로 매핑되어 있어 pmxcfs가 거부합니다.
6.2 1차 조치 (즉시 해결, 그러나 재부팅 시 되돌아감)
sudo sed -i '/127.0.1.1/d' /etc/hosts
echo "192.168.219.128 raspberrypi" | sudo tee -a /etc/hosts
sudo systemctl restart pve-cluster pveproxy
6.3 근본 원인: cloud-init의 update_etc_hosts 모듈
재부팅하면 /etc/hosts가 다시 127.0.1.1로 초기화되는 문제가 있었습니다. 원인은 /etc/cloud/cloud.cfg의 cloud_init_modules 리스트에 update_etc_hosts 모듈이 등록되어 있어서, 매 부팅마다 템플릿 기준으로 /etc/hosts를 재생성하기 때문입니다.
⚠️
manage_etc_hosts: false를cloud.cfg.d/*.cfg오버라이드 파일에 넣는 것만으로는 해결되지 않았습니다 (모듈 자체가 실행되며 무시됨). 모듈 자체를 제거해야 합니다.
6.4 근본 해결
# 원본 백업
sudo cp /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.bak
# update_etc_hosts 모듈 라인 제거
sudo sed -i '/- update_etc_hosts/d' /etc/cloud/cloud.cfg
# 확인 (아무것도 출력되지 않아야 정상)
grep -n "update_etc_hosts" /etc/cloud/cloud.cfg
이후 /etc/hosts를 다시 수정하고 서비스를 재시작합니다.
sudo sed -i '/127.0.1.1/d' /etc/hosts
echo "192.168.219.128 raspberrypi" | sudo tee -a /etc/hosts
sudo systemctl restart pve-cluster pveproxy
systemctl status pve-cluster pveproxy
재부팅 후에도 /etc/hosts가 유지되는지 반드시 재검증합니다.
sudo reboot
# 재접속 후
cat /etc/hosts
grep -n "update_etc_hosts" /etc/cloud/cloud.cfg # 출력 없어야 정상
systemctl status pve-cluster pveproxy # 둘 다 active (running)
7. PXVIRT 패키지 설치
sudo apt install -y proxmox-ve pve-manager qemu-server pve-cluster postfix open-iscsi
- 설치 중 Postfix 메일 설정 창이 뜨면
Local only선택 - 설치 시간은 라즈베리파이 4 기준 수 분 ~ 10분 이상 소요될 수 있음
pve-kernel-*관련 재부팅 안내가 나와도 설치가 끝날 때까지 진행 후 마지막에 한 번만 재부팅
설치 후 핵심 서비스 상태 확인:
pveversion
systemctl status pve-cluster
systemctl status pveproxy
💡
pveproxy가/etc/pve/local/pve-ssl.key: failed to load local private key에러를 내는 경우, 대부분pve-cluster가 죽어서/etc/pve가 마운트되지 않은 것이 원인입니다.pve-cluster를 먼저 정상화하면 자동 해결됩니다.
8. 웹 UI 접속 및 리버스 프록시(NPM) 연동
8.1 로컬 IP로 직접 접속
https://<Proxmox IP>:8006
- 로그인:
root/ 설정한 비밀번호 / RealmLinux PAM standard authentication - 자체서명 인증서 경고(
ERR_CERT_AUTHORITY_INVALID등)는 정상이며 무시하고 진행 가능
pveproxy는 8006 포트에서 HTTPS만 지원하며, plain HTTP 요청은 처리하지 못합니다.http://IP:8006으로 접속하면 정상 응답을 받지 못합니다.
8.2 Nginx Proxy Manager(NPM)로 도메인 연결 시
증상: NPM Scheme을 http로 설정하면 ERR_TOO_MANY_REDIRECTS(리디렉션 무한루프) 발생.
원인: pveproxy가 HTTPS만 지원하는데 NPM이 http로 요청을 보내 정상 응답을 못 받고 루프에 빠짐.
해결: NPM 프록시 호스트 설정에서
- Scheme:
https(가장 중요) - Forward Hostname/IP: Proxmox 서버 IP
- Forward Port:
8006 - Websockets Support: 켜기 (콘솔/noVNC 사용 시 필요)
- Force SSL: 켜기 (프론트엔드용 정식 인증서, 백엔드 자체서명 인증서와는 별개)
참고: NPM(nginx)은 기본적으로 백엔드 인증서 유효성을 검증하지 않으므로(
proxy_ssl_verify기본값이 off) 별도 설정 없이도 자체서명 인증서 백엔드와 정상 연동됩니다.
구조 이해:
[브라우저] --443, https, NPM의 정식 인증서--> [NPM] --8006, https, Proxmox 자체서명 인증서--> [pveproxy]
브라우저는 NPM하고만 통신하고 8006 포트는 NPM-Proxmox 내부 통신에서만 쓰입니다. 그래서 도메인으로 접속하면 인증서 경고 없이 깔끔하게 뜨는 반면, IP로 직접 접속하면 자체서명 인증서 경고가 뜹니다.
9. pveam 앱 템플릿 카탈로그 서명 오류와 arm64 LXC 템플릿 확보
9.1 증상
sudo pveam update 실행 시 다른 소스는 정상인데 PXVIRT 자체 LXC 카탈로그만 서명 검증에 실패했습니다.
signature verification: Missing key 09D2068B4E9AAFF16B27F7FA9A5C09A2D150E209, which is needed to verify signature.
unable to verify signature - command '/usr/bin/sqv --keyring /usr/share/doc/pve-manager/trustedkeys.gpg ...' failed: exit code 1
/var/log/pveam.log로 확인해보면 download.proxmox.com, releases.turnkeylinux.org는 update successful이지만, mirrors.lierfang.com/pxcloud/pxvirt/lxcs(PXVIRT 자체 arm64 LXC 템플릿 카탈로그) 하나만 계속 실패합니다. 그 여파로 sudo pveam available이 아래 에러를 내며 목록 자체를 못 보여주는 경우도 있었습니다.
unable to open file '/var/lib/pve-manager/apl-info/mirrors.lierfang.com' - No such file or directory
9.2 원인
- 이 카탈로그(
lxcs/)는 서버 디렉토리 목록상 2024년 2월 이후로 갱신되지 않은 사실상 방치 상태입니다. - 필요한 서명 키(
09D2068B...D150E209)는 Ubuntu keyserver,keys.openpgp.org, 저장소 자체가 제공하는pveport.gpg/lierfang.gpg(둘 다39DE63C7D57A32124785E63DB859507D6B1F46D3, 별개의 키) 어디에도 존재하지 않습니다. 관리자가 저장소 서명 키를 교체하면서 이 옛 LXC 카탈로그의 서명은 갱신하지 않은 것으로 추정됩니다. - 즉 “정식으로 키를 구해서 신뢰하는” 경로 자체가 막혀 있는 상태이며, 다른 PXVIRT 사용자 후기에서도 동일하게 “arm64 템플릿이 안 보인다”는 증상이 보고되어 있습니다.
9.3 해결 방향 결정
카탈로그 소스 정의는 apt sources.list가 아니라 pve-manager 패키지에 하드코딩되어 있습니다.
sudo grep -n "lierfang" /usr/share/perl5/PVE/APLInfo.pm
get_apl_sources 함수 안의 mirrors.lierfang.com 블록이 원인입니다. 이 블록을 지우면 pveam update는 깨끗하게 성공하지만, 이 카탈로그가 유일한 arm64 LXC 템플릿 소스였기 때문에 arm64 템플릿을 아예 받을 방법이 없어집니다. 따라서 이 블록은 건드리지 않고 그대로 두고, arm64 템플릿은 pveam 카탈로그를 거치지 않고 별도 경로(linuxcontainers.org)에서 수동으로 확보하는 방식을 택했습니다.
💡
pveam update로그에 이 소스에 대한 서명 오류가 계속 남는 것은 무해하며 무시해도 됩니다. 아래 9.4 방식으로 확보한 템플릿은pveam list <storage>(로컬에 이미 있는 템플릿)로 보이는 것이지,pveam available(원격 카탈로그 목록)로 보이는 게 아니기 때문에 이 카탈로그 상태와 무관하게 동작합니다.
9.4 linuxcontainers.org에서 arm64 템플릿 직접 받기
1) 최신 빌드 확인 (예: Debian 12 bookworm)
curl -sL https://images.linuxcontainers.org/images/debian/bookworm/arm64/default/ \
| grep -oP 'href="\K[^"]+' | tail -5
2) rootfs 다운로드 및 무결성 확인 (아래 날짜는 확인한 최신 빌드로 교체)
BUILD="20260709_05%3A24"
cd /var/lib/vz/template/cache/
sudo curl -L "https://images.linuxcontainers.org/images/debian/bookworm/arm64/default/${BUILD}/rootfs.tar.xz" \
-o debian-12-lxcorg-standard_20260709_arm64.tar.xz
curl -sL "https://images.linuxcontainers.org/images/debian/bookworm/arm64/default/${BUILD}/SHA256SUMS" \
| grep rootfs.tar.xz
sha256sum debian-12-lxcorg-standard_20260709_arm64.tar.xz
두 해시값이 일치하는지 반드시 확인합니다.
3) /etc/network 디렉토리 누락 문제 패치
linuxcontainers.org의 default 이미지는 ifupdown 없이 systemd-networkd만 사용하므로 /etc/network/ 디렉토리 자체가 없습니다. PVE의 컨테이너 생성 후크가 이 경로에 임시 파일을 쓰려다 실패합니다.
unable to open file '/etc/network/interfaces.tmp.XXXXX' - No such file or directory
unable to create CT XXX - error in setup task PVE::LXC::Setup::post_create_hook
디스크 여유공간이 있는 위치(RAM 기반 /tmp가 아니라 /var/lib/vz 등)에서 압축을 풀어 빈 디렉토리를 추가한 뒤 재압축합니다.
sudo mkdir -p /var/lib/vz/template/rootfs-patch
cd /var/lib/vz/template/rootfs-patch
sudo tar -xJf /var/lib/vz/template/cache/debian-12-lxcorg-standard_20260709_arm64.tar.xz
sudo mkdir -p etc/network/if-up.d etc/network/if-down.d
sudo tar -cJf /var/lib/vz/template/cache/debian-12-lxcorg-standard_20260709_arm64.tar.xz .
cd /
sudo rm -rf /var/lib/vz/template/rootfs-patch
4) 등록 확인 및 컨테이너 생성 테스트
sudo pveam list local
sudo pct create 999 local:vztmpl/debian-12-lxcorg-standard_20260709_arm64.tar.xz \
--hostname test-arm64 \
--rootfs local:4 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1
sudo pct start 999
sudo pct exec 999 -- uname -m # aarch64가 나와야 정상
정상 확인 후 테스트용 컨테이너는 정리합니다.
sudo pct stop 999
sudo pct destroy 999 --purge
💡 위 패치는 템플릿 파일 자체에 적용되므로, 한 번만 해두면 이후 같은 템플릿으로 컨테이너를 만들 때는 반복할 필요가 없습니다. 다른 배포판(Ubuntu, Alpine 등)의 arm64 템플릿도
images.linuxcontainers.org에서 같은 방식으로 받아 동일하게 패치하면 됩니다.
10. Zigbee2MQTT 컨테이너 구축 (USB 패스스루 + Docker)
10.1 컨테이너 생성
앞서 확보한 arm64 템플릿으로 LXC 컨테이너를 생성합니다 (privileged로 생성 — USB 패스스루가 단순해집니다).
sudo pct create 100 local:vztmpl/debian-12-lxcorg-standard_20260709_arm64.tar.xz \
--hostname zigbee2mqtt \
--rootfs local:4 \
--memory 512 \
--swap 512 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 0
sudo pct start 100
pct config 100에unprivileged항목이 아예 없으면 기본값인 **privileged(0)**로 생성된 것입니다.
10.2 Zigbee USB 동글 패스스루
1) 호스트에서 동글 정보 확인
lsusb
ls -la /dev/serial/by-id/
ls -la /dev/ttyUSB* /dev/ttyACM* 2>/dev/null
/dev/serial/by-id/의 심볼릭 링크(예: usb-1a86_USB_Serial-if00-port0)를 사용합니다. 재부팅해도 /dev/ttyUSB0 같은 번호가 바뀔 수 있는 것과 달리 by-id 경로는 고정적입니다. 디바이스 노드의 major 번호(ttyUSB 계열은 보통 188)도 ls -la /dev/ttyUSB0로 확인해둡니다.
2) 컨테이너 정지 후 .conf에 cgroup 허용 + bind mount 추가
sudo pct stop 100
sudo tee -a /etc/pve/lxc/100.conf > /dev/null <<'EOF'
lxc.cgroup2.devices.allow: c 188:* rwm
lxc.mount.entry: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 dev/ttyUSB0 none bind,optional,create=file 0 0
EOF
sudo pct start 100
3) 컨테이너 안에서 확인
sudo pct exec 100 -- ls -la /dev/ttyUSB0
호스트와 동일한 major/minor 번호(188, 0)로 보이면 성공입니다.
10.3 네트워크 미연결 문제 (firewall=1로 인한 브릿지 우회 경로)
증상: 컨테이너 안에서 게이트웨이조차 Destination Host Unreachable로 통신 불가.
sudo pct exec 100 -- ping -c 3 192.168.219.1
# From 192.168.219.140 icmp_seq=1 Destination Host Unreachable
원인: pct create 시 기본으로 붙는 net0의 firewall=1 옵션 때문에, 컨테이너의 veth가 vmbr0에 직접 붙지 않고 **fwbr100i0(방화벽 중계 브릿지)**를 거치는 구조로 생성됩니다. pve-firewall status가 disabled인데도 이 중계 브릿지의 규칙이 꼬여 트래픽이 막힐 수 있습니다.
bridge link show
# veth100i0@eth0 ... master fwbr100i0 ← vmbr0에 직접 안 붙어있음
해결: firewall=1 옵션을 제거하고 vmbr0에 직접 연결합니다 (클러스터 방화벽 자체가 disabled인 환경이면 개별 컨테이너 방화벽도 끄는 게 자연스럽습니다).
sudo pct set 100 -net0 name=eth0,bridge=vmbr0,hwaddr=<기존 MAC>,type=veth
bridge link show
# veth100i0@eth0 ... master vmbr0 ← 직접 연결됨
sudo pct exec 100 -- ping -c 3 192.168.219.1 # 정상 응답 확인
10.4 Docker 설치 (Zigbee2MQTT를 컨테이너로 실행하기 위한 nesting)
LXC 안에서 Docker 컨테이너를 띄우려면 nesting 기능을 먼저 켜야 합니다.
sudo pct stop 100
sudo pct set 100 -features nesting=1,keyctl=1
sudo pct start 100
Docker 공식 스크립트로 설치하고 정상 동작을 확인합니다.
sudo pct exec 100 -- bash -c "curl -fsSL https://get.docker.com | sh"
sudo pct exec 100 -- docker run --rm hello-world
Hello from Docker! 메시지가 나오면 nesting과 Docker 모두 정상입니다.
10.5 Dockge 설치 및 Zigbee2MQTT 배포 (Dockge로 관리)
여러 Docker Compose 스택을 CLI로 직접 관리하는 대신, 웹 UI 기반 관리 도구 Dockge를 먼저 설치합니다. Dockge는 스택마다 /opt/stacks/<스택명>/compose.yaml 형태로 디렉토리를 분리해서 관리하며, 이렇게 만들어두면 이후 Matterbridge 등 다른 스택도 같은 방식으로 추가할 수 있습니다.
1) Dockge 자체를 위한 compose.yaml 작성 및 실행
sudo pct exec 100 -- mkdir -p /opt/stacks/dockge
sudo pct exec 100 -- bash -c 'cat > /opt/stacks/dockge/compose.yaml <<EOF
services:
dockge:
image: louislam/dockge:1
container_name: dockge
restart: unless-stopped
ports:
- 5001:5001
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/stacks/dockge/data:/app/data
- /opt/stacks:/opt/stacks
environment:
- DOCKGE_STACKS_DIR=/opt/stacks
EOF'
sudo pct exec 100 -- bash -c "cd /opt/stacks/dockge && docker compose up -d"
/opt/stacks:/opt/stacks마운트가 핵심입니다. Dockge 컨테이너 자신도 호스트(LXC 컨테이너 100번)의/opt/stacks디렉토리를 그대로 바라봐야, 웹 UI에서 만든 스택들이 실제로 그 경로에 파일로 저장되고docker compose명령도 정상 동작합니다.
브라우저에서 http://<LXC 컨테이너 IP>:5001로 접속해 최초 관리자 계정을 생성합니다.
2) Dockge 웹 UI로 Zigbee2MQTT 스택 생성
Dockge 웹 UI에서 “+ Compose” → 스택 이름 zigbee2mqtt 입력 후 아래 내용을 붙여넣습니다. (CLI로 직접 파일을 만들어도 동일하게 동작합니다.)
sudo pct exec 100 -- mkdir -p /opt/stacks/zigbee2mqtt/data
sudo pct exec 100 -- bash -c 'cat > /opt/stacks/zigbee2mqtt/compose.yaml <<EOF
services:
zigbee2mqtt:
container_name: zigbee2mqtt
image: koenkk/zigbee2mqtt
restart: unless-stopped
volumes:
- ./data:/app/data
- /run/udev:/run/udev:ro
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
ports:
- 8099:8099
environment:
- TZ=Asia/Seoul
EOF'
./data는 이compose.yaml이 위치한/opt/stacks/zigbee2mqtt/기준으로 해석되므로/opt/stacks/zigbee2mqtt/data와 동일합니다. Dockge가 스택별로 폴더를 나눠 관리하는 구조와 자연스럽게 맞물립니다.
3) configuration.yaml 작성 (외부 MQTT 브로커 접속 정보 반영)
sudo pct exec 100 -- bash -c 'cat > /opt/stacks/zigbee2mqtt/data/configuration.yaml <<EOF
homeassistant: false
permit_join: true
mqtt:
base_topic: zigbee2mqtt
server: mqtt://<MQTT 브로커 IP>:1883
user: <MQTT 계정>
password: <MQTT 비밀번호>
serial:
port: /dev/ttyUSB0
adapter: zstack
frontend:
port: 8099
advanced:
network_key: GENERATE
EOF'
⚠️
serial.adapter명시 필수: CH340 같은 범용 USB-시리얼 변환칩 뒤에 실제 Zigbee 라디오 칩(CC2652 등)이 붙은 동글은 zigbee-herdsman의 자동판별(USB VID/PID 기반)이 실패하는 경우가 많습니다.Error: USB adapter discovery error (No valid USB adapter found).이 에러가 나오면 어댑터 칩셋에 맞는 값(TI zstack 계열 →
zstack, Silicon Labs EmberZNet →ember, deCONZ →deconz, ZBOSS →zboss)을serial.adapter에 직접 지정하면 해결됩니다.
⚠️
frontend.port와compose.yaml의 포트 매핑은 반드시 동일한 숫자로 맞춰야 합니다. 처음엔frontend.port: 8080+ 매핑8099:8080(호스트 8099 → 컨테이너 내부 8080)으로 구성했다가, 이후 포트를 바꾸는 과정에서configuration.yaml의frontend.port만 8099로 바뀌고 매핑은 예전 값(8099:8080) 그대로 남아 컨테이너 내부 리스닝 포트(8099)와 매핑 대상(8080)이 어긋나는 문제가 발생했습니다. 증상은curl로 접속 시Recv failure: Connection reset by peer, 브라우저에서는ERR_CONNECTION_REFUSED로 나타납니다.진단 방법: 컨테이너 내부에서 실제 리스닝 포트를 직접 확인합니다.
sudo pct exec 100 -- docker exec zigbee2mqtt sh -c "netstat -tln 2>/dev/null || ss -tln"결론: 혼동을 피하기 위해 내부/외부 포트를 동일한 값(예: 8099:8099)으로 통일하는 것을 권장합니다. 포트를 바꿀 때는
configuration.yaml의frontend.port와compose.yaml의 포트 매핑 양쪽을 항상 같이 수정하고, 변경 후에는 Dockge 웹 UI의 “Restart” 버튼이 아니라 **”Deploy”(=docker compose up -d에 해당)**로 재적용해야 합니다 (포트 매핑 변경은 단순 재시작만으로는 반영되지 않습니다).
4) 실행 및 로그 확인
Dockge 웹 UI에서 zigbee2mqtt 스택의 “Deploy” 버튼을 누르거나, CLI로 직접 실행합니다.
sudo pct exec 100 -- bash -c "cd /opt/stacks/zigbee2mqtt && docker compose up -d"
sudo pct exec 100 -- bash -c "cd /opt/stacks/zigbee2mqtt && docker compose logs --tail 50"
zh:zstack:znp: Serialport opened 이후 Connected to MQTT server, Zigbee2MQTT started!까지 정상적으로 이어지면 성공입니다.
10.6 웹 UI 접속
http://<LXC 컨테이너 IP>:8099 # Zigbee2MQTT
http://<LXC 컨테이너 IP>:5001 # Dockge (스택 관리)
10.7 재발한 네트워크 단절의 근본 원인: NetworkManager의 netplan-eth0 프로필
10.3에서 firewall=1 제거로 브릿지 연결 문제를 해결했지만, 이후 컨테이너를 재시작할 때마다 같은 증상(게이트웨이/외부 통신 불가, ENETUNREACH)이 재발했습니다. 원인은 10.3과 달랐습니다.
증상: 컨테이너 재시작 후 ip addr show eth0에 IPv4 주소가 아예 없고(inet6 link-local만 존재), DHCP도 응답을 못 받음. bridge link show에도 veth100i0이 보이지 않음.
원인 분석: journalctl -f로 컨테이너 시작 시점의 로그를 실시간으로 관찰한 결과, NetworkManager가 새로 생성된 veth100i0을 감지하자마자 기존 netplan-eth0 연결 프로필을 자동으로 적용하며 **vmbr0에서 강제로 분리(detached bridge port veth100i0)**시키는 것을 확인했습니다.
NetworkManager: policy: auto-activating connection 'netplan-eth0' ...
NetworkManager: device (veth100i0): Activation: starting connection 'netplan-eth0' ...
NetworkManager: device (vmbr0): detached bridge port veth100i0
nmcli connection show netplan-eth0 | grep interface-name
# connection.interface-name: -- ← 특정 인터페이스 이름에 고정되어 있지 않음!
netplan-eth0 프로필이 interface-name을 지정하지 않고 type=ethernet으로만 정의되어 있어서, “이름과 무관하게 관리되지 않는 이더넷 타입 장치가 나타나면 전부 가져가는” 동작을 하고 있었습니다. 4장에서 eth0만 unmanaged로 지정했던 규칙이 LXC가 만드는 veth* 장치에는 적용되지 않았던 것이 근본 원인입니다.
근본 해결: unmanaged-devices 규칙에 veth* 패턴을 추가합니다.
sudo tee /etc/NetworkManager/conf.d/unmanaged-eth0.conf > /dev/null <<EOF
[keyfile]
unmanaged-devices=interface-name:eth0;interface-name:veth* EOF sudo systemctl restart NetworkManager
적용 후 컨테이너를 재시작해 브릿지 연결이 유지되는지 확인합니다.
sudo pct stop 100
sudo pct start 100
sleep 3
bridge link show
# veth100i0@eth0 ... master vmbr0 ← 재시작 후에도 유지되어야 정상
sudo pct exec 100 -- ip addr show eth0 # IPv4 정상 할당 확인
💡 이 문제는 zigbee2mqtt 컨테이너뿐 아니라 앞으로 만들 모든 LXC/VM 컨테이너에 동일하게 영향을 줍니다.
unmanaged-eth0.conf는 한 번만 고쳐두면 이후 생성하는 모든 컨테이너에 자동 적용됩니다.
10.8 Zigbee2MQTT 업그레이드
Docker Compose로 배포했으므로 이미지만 새로 받아 재생성하면 됩니다. Dockge 웹 UI에서는 스택 페이지의 “Pull” → “Deploy” 버튼으로 아래 2~4단계를 한 번에 처리할 수 있습니다. 아래는 CLI로 동일하게 수행하는 방법입니다.
1) 업그레이드 전 데이터 백업 (설정, 페어링된 기기 정보, 네트워크 키가 담긴 data 디렉토리)
sudo pct exec 100 -- tar -czf /opt/stacks/zigbee2mqtt/data-backup-$(date +%Y%m%d).tar.gz -C /opt/stacks/zigbee2mqtt data
2) 현재 버전 확인 (업그레이드 전후 비교용)
sudo pct exec 100 -- docker exec zigbee2mqtt cat /app/package.json | grep '"version"'
3) 최신 이미지 받기
sudo pct exec 100 -- bash -c "cd /opt/stacks/zigbee2mqtt && docker compose pull"
4) 새 이미지로 컨테이너 재생성
sudo pct exec 100 -- bash -c "cd /opt/stacks/zigbee2mqtt && docker compose up -d"
5) 로그 확인 (마이그레이션 노트 유무 확인)
sudo pct exec 100 -- bash -c "cd /opt/stacks/zigbee2mqtt && docker compose logs --tail 50"
Zigbee2MQTT started!가 뜨고, migration-N-to-M.log 관련 메시지가 있다면 메이저 버전 업그레이드로 설정 스키마가 바뀐 것이니 /app/data/에 저장된 마이그레이션 노트를 확인합니다.
6) 새 버전 확인
sudo pct exec 100 -- docker exec zigbee2mqtt cat /app/package.json | grep '"version"'
롤백이 필요한 경우:
sudo pct exec 100 -- bash -c "cd /opt/stacks/zigbee2mqtt && docker compose down"
sudo pct exec 100 -- tar -xzf /opt/stacks/zigbee2mqtt/data-backup-<날짜>.tar.gz -C /opt/stacks/zigbee2mqtt
# compose.yaml의 image 태그를 이전 버전(예: koenkk/zigbee2mqtt:2.12.1)으로 고정 후
sudo pct exec 100 -- bash -c "cd /opt/stacks/zigbee2mqtt && docker compose up -d"
💡 특정 버전에 고정하려면
image: koenkk/zigbee2mqtt대신image: koenkk/zigbee2mqtt:2.13.0처럼 태그를 명시합니다. 태그를 생략(latest)하면docker compose pull(또는 Dockge의 “Pull” 버튼)을 실행할 때마다 최신 버전을 받아옵니다.
10.9 컨테이너 DHCP → 고정 IP 전환 시 systemd-networkd 충돌
증상: LXC 컨테이너(100번)에 고정 IP를 부여하려고 pct set으로 net0을 수정했는데도, 컨테이너 안에서는 계속 다른(예전) IP가 dynamic(DHCP)으로 잡힘.
sudo pct set 100 -net0 name=eth0,bridge=vmbr0,ip=192.168.219.140/24,gw=192.168.219.1,type=veth
sudo pct exec 100 -- ip addr show eth0
# inet 192.168.219.142/24 ... scope global dynamic eth0 ← .140이 아니라 .142, 게다가 dynamic
원인: pct config 100으로 보면 호스트 쪽 net0은 정확히 ip=192.168.219.140/24로 반영되어 있었습니다. 문제는 컨테이너 내부에 있었습니다. 9.4에서 확인했듯 images.linuxcontainers.org의 default 템플릿은 ifupdown 없이 systemd-networkd만으로 네트워크를 관리합니다. /etc/network/interfaces에 static 설정을 직접 적어도, 그걸 처리할 networking.service 자체가 없어서(Unit networking.service could not be found) 완전히 무시되고, 대신 /etc/systemd/network/eth0.network에 기본값으로 박혀있던 DHCP=true가 실제로 적용되고 있었습니다.
sudo pct exec 100 -- systemctl is-enabled systemd-networkd # enabled
sudo pct exec 100 -- ps aux | grep networkd # /lib/systemd/systemd-networkd 프로세스 확인
sudo pct exec 100 -- cat /etc/systemd/network/eth0.network
# [Network]
# DHCP=true ← 이게 실제로 우선 적용되고 있던 값
해결: /etc/network/interfaces가 아니라 /etc/systemd/network/eth0.network를 직접 static으로 고쳐야 합니다.
sudo pct exec 100 -- bash -c 'cat > /etc/systemd/network/eth0.network <<EOF
[Match]
Name=eth0
[Network]
Address=192.168.219.140/24
Gateway=192.168.219.1
DNS=192.168.219.1
EOF'
sudo pct exec 100 -- systemctl restart systemd-networkd
재시작 직후에는 기존 DHCP 임대(.142)가 secondary로 남아 두 IP가 동시에 잡히는 경우가 있으므로, 주소를 완전히 비운 뒤 재적용해서 정리합니다.
sudo pct exec 100 -- ip addr flush dev eth0
sudo pct exec 100 -- systemctl restart systemd-networkd
sleep 3
sudo pct exec 100 -- ip addr show eth0
# inet 192.168.219.140/24 ... scope global eth0 ← dynamic 표시 없이 하나만 남아야 정상
sudo pct exec 100 -- ip route
# default via 192.168.219.1 dev eth0 proto static
💡
/etc/network/interfaces에 static 설정을 적어두는 것 자체는 무해하지만, 이 템플릿에서는 아무 효과가 없습니다. systemd-networkd 기반 이미지에서는.network파일이 유일한 진실의 원천(source of truth)입니다.
10.10 콘솔 root 로그인 실패 (Login incorrect) — 계정 잠금
증상: 웹 UI(noVNC) 콘솔에서 root / 설정한 비밀번호로 로그인해도 Login incorrect.
원인: linuxcontainers.org 템플릿은 root 비밀번호가 아예 설정되지 않고 계정 자체가 잠긴(locked) 상태로 배포됩니다.
sudo pct enter 100 # 비밀번호 없이 호스트에서 컨테이너 root 셸로 직접 진입 가능
passwd -S root # → root L 2026-07-11 0 99999 7 -1 (L = locked)
grep root /etc/shadow # → root:*:20645:... (해시가 * 라 어떤 비밀번호도 매칭 안 됨)
해결: pct enter는 잠금 상태와 무관하게 진입할 수 있으므로, 이 안에서 비밀번호를 새로 설정하면 잠금도 함께 풀립니다.
sudo pct enter 100
passwd root # 새 비밀번호 두 번 입력
passwd -S root # → root P ... (P = usable password set) 확인
exit
이후 콘솔에서 root / 새 비밀번호로 정상 로그인됩니다.
10.11 IPv6 link-local 미생성 (Matterbridge mDNS 경고)
증상: Matterbridge 로그에 아래 경고가 뜸.
[SystemCheck] System Check: No IPv6 network interface found. Check your network configuration.
[SystemCheck] System Check: Use --mdnsinterface parameter or set Mdns interface in Settings ...
Matter는 mDNS로 기기를 찾을 때 IPv6 link-local(fe80::...) 주소를 우선 사용하는데, 10.9에서 eth0.network를 static IPv4 전용으로 새로 쓰면서 IPv6 관련 설정이 통째로 빠졌던 것이 원인입니다.
진단: disable_ipv6, accept_ra, autoconf, addr_gen_mode 등 관련 sysctl은 전부 정상(활성화) 값이었는데도 eth0만 유독 /proc/net/if_inet6에 나타나지 않았습니다. 같은 컨테이너 안의 Docker 브릿지(br-*)나 veth들은 모두 Gained IPv6LL 로그가 찍히는데 eth0만 재시작 로그에서 빠져있어, systemd-networkd의 자동 EUI64 link-local 생성이 이 인터페이스에서만 완료되지 못하고 있음을 확인했습니다. 수동으로 주소를 추가하면 즉시 성공하는 것으로 커널/LXC 권한 문제가 아님을 확인했습니다.
sudo pct exec 100 -- ip -6 addr add fe80::1/64 dev eth0 # 즉시 성공 → 자동생성 로직만 문제
해결: 자동 생성에 의존하지 말고 link-local 주소를 .network 파일에 명시적 static으로 고정합니다.
sudo pct exec 100 -- bash -c 'cat > /etc/systemd/network/eth0.network <<EOF
[Match]
Name=eth0
[Network]
Address=192.168.219.140/24
Address=fe80::1/64
Gateway=192.168.219.1
DNS=192.168.219.1
EOF'
sudo pct exec 100 -- systemctl restart systemd-networkd
sudo pct exec 100 -- ip addr show eth0 # inet6 fe80::1/64 확인
적용 후 Matterbridge 컨테이너를 재시작하면 경고가 사라집니다.
sudo pct exec 100 -- docker restart matterbridge
sudo pct exec 100 -- docker logs matterbridge --tail 20
💡 이 경고는 mDNS 커미셔닝에 영향을 줄 수 있어 고쳤지만, IPv4 mDNS만으로도 대부분의 기기 페어링이 동작하는 경우가 많아 당장 실사용에 지장이 없다면 급하게 고치지 않아도 되는 항목입니다.
10.12 Matterbridge 추가 (동일하게 Dockge로 관리)
10.5에서 구성한 Dockge에 스택을 하나 더 추가하는 것만으로 Matterbridge도 동일한 방식으로 배포할 수 있습니다.
# /opt/stacks/matterbridge/compose.yaml
services:
matterbridge:
container_name: matterbridge
image: luligu/matterbridge:latest
network_mode: host
restart: always
volumes:
- /opt/stacks/matterbridge/Matterbridge:/root/Matterbridge
- /opt/stacks/matterbridge/.matterbridge:/root/.matterbridge
ports:
- 8283:8283
networks: {}
compose.yaml의./data같은 상대경로는 compose 파일이 위치한 디렉토리 기준으로 해석되므로, Dockge가 스택별로 폴더를 나눠 관리하는 구조와 자연스럽게 맞물립니다 (Zigbee2MQTT의./data도 같은 원리).network_mode: host를 쓰는 이유는 Matterbridge/Matter 프로토콜이 mDNS 브로드캐스트와 다수의 임의 포트를 사용하기 때문에, 별도 브릿지 네트워크로 포트만 개별 매핑하는 방식보다 호스트 네트워크를 직접 쓰는 쪽이 훨씬 간단하고 안정적이기 때문입니다.- zigbee2mqtt의
/run/udev:/run/udev:ro마운트와 같은 이유로, USB/시리얼 장치를 다루는 컨테이너는 udev 메타데이터(벤더/제품 ID, 시리얼 넘버,/dev/serial/by-id/...심볼릭 링크 정보)를 읽기 전용으로 참조할 수 있어야 자동 감지 기능이 정상 동작합니다.
11. 최종 설정 파일 모음
/etc/apt/sources.list.d/pxvirt-sources.list
deb https://mirrors.lierfang.com/pxcloud/pxvirt trixie main
/etc/network/interfaces
# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 192.168.219.128/24
gateway 192.168.219.1
dns-nameservers 192.168.219.130
/etc/NetworkManager/conf.d/unmanaged-eth0.conf
[keyfile]
unmanaged-devices=interface-name:eth0;interface-name:veth*
처음엔
eth0만 지정했으나, LXC 컨테이너의veth*인터페이스를netplan-eth0프로필이 탈취하는 문제(10.7 참고)가 발견되어veth*패턴을 추가했습니다.
/etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.219.128 raspberrypi
/etc/cloud/cloud.cfg (관련 부분만)
cloud_init_modules 리스트에서 update_etc_hosts 항목 제거됨 (update_hostname 다음이 바로 ca_certs로 이어짐).
/opt/stacks/dockge/compose.yaml (LXC 100번 컨테이너 내부)
services:
dockge:
image: louislam/dockge:1
container_name: dockge
restart: unless-stopped
ports:
- 5001:5001
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/stacks/dockge/data:/app/data
- /opt/stacks:/opt/stacks
environment:
- DOCKGE_STACKS_DIR=/opt/stacks
/opt/stacks/zigbee2mqtt/compose.yaml (LXC 100번 컨테이너 내부, Dockge로 관리)
services:
zigbee2mqtt:
container_name: zigbee2mqtt
image: koenkk/zigbee2mqtt
restart: unless-stopped
volumes:
- ./data:/app/data
- /run/udev:/run/udev:ro
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
ports:
- 8099:8099
environment:
- TZ=Asia/Seoul
/etc/pve/lxc/100.conf (zigbee2mqtt, 관련 부분만)
features: nesting=1,keyctl=1
net0: name=eth0,bridge=vmbr0,gw=192.168.219.1,hwaddr=BC:24:11:2A:4A:D4,ip=192.168.219.140/24,type=veth
lxc.cgroup2.devices.allow: c 188:* rwm
lxc.mount.entry: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 dev/ttyUSB0 none bind,optional,create=file 0 0
처음엔
ip=dhcp로 생성했으나, 10.9에서 고정 IP(192.168.219.140/24)로 전환했습니다. 호스트 쪽 이 설정만으로는 컨테이너 내부 IP가 바뀌지 않으며, 아래eth0.network파일도 함께 맞춰야 합니다.
/etc/systemd/network/eth0.network (LXC 100번 컨테이너 내부)
[Match]
Name=eth0
[Network]
Address=192.168.219.140/24
Address=fe80::1/64
Gateway=192.168.219.1
DNS=192.168.219.1
이 템플릿은
ifupdown이 아닌 systemd-networkd로 네트워크를 관리하므로 (9.4 참고),/etc/network/interfaces가 아니라 이 파일이 실제 진실의 원천입니다.Address=fe80::1/64는 자동 IPv6 link-local 생성이 이 인터페이스에서 실패하는 문제(10.11)를 우회하기 위해 명시적으로 추가한 값입니다.
/opt/stacks/matterbridge/compose.yaml (LXC 100번 컨테이너 내부, Dockge로 관리)
services:
matterbridge:
container_name: matterbridge
image: luligu/matterbridge:latest
network_mode: host
restart: always
volumes:
- /opt/stacks/matterbridge/Matterbridge:/root/Matterbridge
- /opt/stacks/matterbridge/.matterbridge:/root/.matterbridge
ports:
- 8283:8283
networks: {}
12. 트러블슈팅 요약표
| 증상 | 원인 | 해결 |
|---|---|---|
apt update 시 521 에러, not signed | pveport 저장소 자체가 폐지/서버 다운 | PXVIRT로 전환 |
| GPG 키 파일이 16바이트 | 죽은 서버의 에러 응답이 저장됨 | 새 키 재다운로드 |
| NetworkManager 비활성화 후 완전 접속 불가 | ifupdown2 준비 전에 NM을 꺼서 아무도 인터페이스를 관리 안 함 | eth0만 unmanaged 처리, WiFi는 NM 유지 |
systemctl restart networking 중 SSH 순간 끊김 | 네트워크 스택 재시작으로 라우팅 테이블 일시 흔들림 | 정상 현상, 재접속하면 됨 |
eth0이 NO-CARRIER | 케이블 미연결 | 케이블 연결 |
apt update에서 Temporary failure resolving | resolvconf 미설치로 DNS 미반영 | /etc/resolv.conf 수동 작성 → resolvconf 설치 |
/etc/resolv.conf에 # Generated by NetworkManager만 있고 비어있음 | eth0을 unmanaged로 뺀 후 NM이 DNS 정보를 못 가져옴 | resolvconf 설치로 ifupdown2가 관리하게 함 |
pve-cluster 계속 재시작 실패, Unable to resolve node name | /etc/hosts가 호스트명을 루프백(127.0.1.1)으로 매핑 | 실제 IP로 매핑 수정 |
재부팅하면 /etc/hosts가 다시 127.0.1.1로 되돌아감 | cloud-init의 update_etc_hosts 모듈이 매 부팅마다 재생성 | cloud.cfg에서 해당 모듈 라인 제거 |
NetworkManager is not running | 이전 스크립트 실행 중 disable된 적이 있음 | systemctl enable --now NetworkManager |
웹 UI ERR_CERT_AUTHORITY_INVALID | Proxmox 자체서명 인증서 | 정상 현상, 무시하고 진행 |
NPM 경유 접속 시 ERR_TOO_MANY_REDIRECTS | NPM Scheme이 http로 설정되어 HTTPS 전용 pveproxy와 통신 실패 | NPM Scheme을 https로 변경 |
pveproxy의 SSL 키 로드 실패 | pve-cluster 실패로 /etc/pve 미마운트 | pve-cluster 문제 해결 시 자동 해결 |
pveam update에서 Missing key ...D150E209 서명 오류 | PXVIRT 자체 LXC 카탈로그(lxcs/)가 2024년 이후 방치되어 서명 키가 어디에도 공개 재배포되지 않음 | 해당 소스는 그대로 두고 무시, arm64 템플릿은 linuxcontainers.org에서 수동 확보 (9장 참고) |
pveam available이 통째로 실패 (unable to open file .../mirrors.lierfang.com) | 위 서명 오류로 해당 소스의 캐시 파일이 아예 생성되지 않음 | pveam list <storage>로 로컬에 이미 등록된 템플릿만 확인 (원격 카탈로그 목록과 무관하게 동작) |
pct create 시 unable to open file '/etc/network/interfaces.tmp...' | linuxcontainers.org default 이미지에 ifupdown//etc/network 디렉토리 자체가 없음 (systemd-networkd만 사용) | 템플릿 압축 해제 후 빈 etc/network/ 디렉토리 추가하여 재압축 (9.4 참고) |
컨테이너 안에서 게이트웨이조차 Destination Host Unreachable | net0의 firewall=1로 veth가 vmbr0이 아닌 fwbr* 중계 브릿지를 거침 | pct set으로 net0에서 firewall=1 제거, vmbr0 직결 (10.3 참고) |
Docker docker run 시 컨테이너가 안 뜨거나 nesting 관련 실패 | LXC features에 nesting=1이 없어 컨테이너 안에서 컨테이너 실행 불가 | pct set 100 -features nesting=1,keyctl=1 |
Zigbee2MQTT: USB adapter discovery error (No valid USB adapter found) | zigbee-herdsman이 USB VID/PID로 어댑터 종류를 자동판별하지 못함 | configuration.yaml의 serial.adapter에 칩셋 값(zstack, ember, deconz 등) 명시 |
docker exec: Container ... is restarting / procReady not received | 컨테이너가 크래시 후 재시작 루프에 빠져 exec 불가 | docker update --restart=no <name>으로 루프 정지 후 docker inspect/docker run --rm으로 별도 진단 |
컨테이너 재시작 시마다 게이트웨이/외부 통신 불가 재발 (ENETUNREACH, IPv4 미할당) | NetworkManager의 netplan-eth0 프로필이 interface-name 미지정으로 새로 생기는 모든 veth*를 탈취하여 vmbr0에서 분리 | unmanaged-devices에 interface-name:veth* 패턴 추가 (10.7 참고, 모든 컨테이너에 영구 적용됨) |
Zigbee2MQTT 웹 UI ERR_CONNECTION_REFUSED / curl: Recv failure: Connection reset by peer | configuration.yaml의 frontend.port와 compose.yaml의 포트 매핑이 서로 다른 값으로 어긋남 | 두 값을 동일한 포트로 통일(예: 8099:8099), 변경 후 Dockge의 “Deploy”(= docker compose up -d)로 재적용 (“Restart”만으로는 매핑 변경 미반영) |
pct set으로 net0에 고정 IP를 줬는데 컨테이너 안에서는 계속 예전 IP가 dynamic으로 잡힘 | 템플릿이 systemd-networkd 기반이라 /etc/network/interfaces가 무시되고, /etc/systemd/network/eth0.network의 DHCP=true가 실제 적용됨 | eth0.network를 직접 static으로 수정, ip addr flush dev eth0 후 재시작으로 기존 DHCP 임대 제거 (10.9 참고) |
콘솔에서 root 로그인 시 Login incorrect | linuxcontainers.org 템플릿은 root 계정이 비밀번호 없이 잠긴(locked) 상태로 배포됨 | sudo pct enter 100으로 비밀번호 없이 진입 후 passwd root로 새로 설정 (잠금도 함께 해제됨, 10.10 참고) |
Matterbridge 로그에 No IPv6 network interface found | eth0.network를 IPv4 static 전용으로 새로 쓰면서 IPv6 자동 link-local 생성이 이 인터페이스에서만 실패 | eth0.network에 Address=fe80::1/64를 명시적으로 추가 (10.11 참고). 실사용 커미셔닝에 문제 없다면 방치해도 무방 |
docker compose의 ./data:/app/data 같은 상대경로가 어느 절대경로를 가리키는지 헷갈림 | 상대경로는 항상 compose 파일이 위치한 디렉토리 기준으로 해석됨 (실행 디렉토리와 무관) | Dockge처럼 스택별로 /opt/stacks/<이름>/compose.yaml 구조를 쓰면 zigbee2mqtt의 ./data = /opt/stacks/zigbee2mqtt/data로 일관되게 대응 (10.5, 10.12 참고) |
13. 재부팅 후 검증 체크리스트
sudo reboot
재접속 후 아래를 모두 확인합니다.
ip addr show eth0 # 정적 IP 유지 확인
nmcli device status # eth0: unmanaged, wlan0: connected
cat /etc/resolv.conf # DNS 서버 유지 확인
cat /etc/hosts # 127.0.1.1 없이 실제 IP 매핑 유지 확인
grep "update_etc_hosts" /etc/cloud/cloud.cfg # 출력 없어야 정상
systemctl status pve-cluster pveproxy # 둘 다 active (running)
sudo pveam list local # 등록된 arm64 템플릿 확인
sudo pct status 100 # zigbee2mqtt 컨테이너 running 확인
bridge link show # veth100i0이 master vmbr0으로 유지되는지 확인
sudo pct exec 100 -- docker compose -f /opt/stacks/zigbee2mqtt/compose.yaml ps
sudo pct exec 100 -- ip addr show eth0 # 192.168.219.140/24 static 유지, dynamic 표시 없어야 정상
sudo pct exec 100 -- ip addr show eth0 | grep fe80 # IPv6 link-local(fe80::1) 유지 확인
sudo pct exec 100 -- passwd -S root # P(usable) 상태 유지 확인, L(locked)이면 잠김
sudo pct exec 100 -- docker ps # zigbee2mqtt, dockge, matterbridge 모두 Up 확인
브라우저에서:
https://<Proxmox IP>:8006접속 → 로그인 화면 정상 표시 (콘솔은root/ 10.10에서 재설정한 비밀번호로 로그인)- (NPM 사용 시)
https://<도메인>접속 → 로그인 화면 정상 표시 http://<zigbee2mqtt 컨테이너 IP>:8099접속 → Zigbee2MQTT 웹 UI 정상 표시http://<컨테이너 IP>:5001접속 → Dockge 웹 UI 정상 표시http://<컨테이너 IP>:8283접속 → Matterbridge 웹 UI 정상 표시, 로그에 IPv6 경고 없는지 확인
모두 통과하면 설치가 안정적으로 완료된 것입니다.