무색
기술블로그
에세이
연구
소개

무색

소프트웨어로 비즈니스의 가능성을 만듭니다. 웹·앱 개발, 음성 AI, 자동화 콘텐츠 제작까지 — 기술이 필요한 곳에 무색이 있습니다.

연락처

contact@museck.com

사업자 정보

상호: 무색

대표: 배성재

사업자등록번호: 577-58-00836

인천광역시 연수구 인천타워대로 323, 에이동 8층 801-802호 AB-132 (송도동, 송도 센트로드)

© 2026 무색. All rights reserved.
개인정보처리방침·이용약관·연락처
INCHEON, KR
Umami Analytics K8s 셀프호스팅 — sumi-e 키 비주얼
홈랩 삽질기
2026. 2. 2.

Umami Analytics를 K8s에 셀프호스팅하기: GA 없이 웹 분석하는 법

umamianalyticskubernetesselfhostingprivacyhomelab

museck.com에 트래픽 분석이 필요했다. 누가 어떤 글을 읽는지, 어디서 유입되는지 정도만 알면 됐다. Google Analytics를 붙이면 5분이면 끝나지만, GDPR 이슈로 쿠키 동의 배너를 달아야 하고 사용자 데이터가 구글 서버로 넘어간다. 개인 블로그 수준에서 그 정도 트레이드오프를 감수할 필요는 없다고 봤다.

그래서 오픈소스 웹 분석 도구인 Umami를 골랐다. 쿠키를 안 쓰고, 수집 데이터가 내 서버에만 남고, 트래킹 스크립트가 2KB도 안 된다. 이미 K8s 클러스터가 돌고 있으니 거기 올리면 된다.

왜 Umami인가

셀프호스팅 웹 분석 도구는 Umami, Plausible, Matomo 등 여러 개가 있다. Umami를 선택한 이유는 간단하다.

  • 쿠키 불필요. GDPR 동의 배너 없이 바로 수집 가능. 방문자 식별은 IP + User Agent 해시 기반이라 개인정보에 해당하지 않는다.
  • 가벼움. 트래킹 스크립트가 2KB 미만. GA의 45KB와 비교하면 페이지 로딩에 미치는 영향이 거의 없다.
  • 셀프호스팅. PostgreSQL 하나면 되고, Docker 이미지 공식 지원. K8s에 올리기 딱 좋다.
  • 대시보드가 깔끔하다. 페이지뷰, 방문자, 리퍼러, 브라우저, OS, 국가별 통계를 한눈에 보여준다. GA처럼 기능이 과하지 않다.

K8s 배포 구성

전체 구성은 단일 YAML 파일 하나로 끝냈다. Namespace, ConfigMap, Secret, PostgreSQL, Umami 앱, Service, IngressRoute까지 총 213줄. Kustomize나 Helm 없이 그냥 kubectl apply 한 방이면 된다.

아키텍처

흐름은 단순하다. 브라우저에서 트래킹 이벤트가 발생하면 Traefik IngressRoute를 거쳐 Umami 앱으로 들어가고, Umami가 PostgreSQL에 저장한다.

브라우저 (museck.com)
  → Traefik IngressRoute (HTTPS)
    → Umami 앱 (ghcr.io/umami-software/umami)
      → PostgreSQL 16 (PVC 영구 스토리지)

PostgreSQL 배포

Umami의 데이터 저장소로 PostgreSQL 16 Alpine을 썼다. PVC로 데이터를 영구 보관하고, nodeSelector로 인프라 노드(worker1)에 고정했다. PVC를 쓰니까 파드가 재시작되어도 데이터가 날아가지 않는다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: umami
spec:
  replicas: 1
  template:
    spec:
      nodeSelector:
        node-role: infra
      containers:
        - name: postgres
          image: postgres:16-alpine
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data

Umami 앱 배포

Umami 앱은 공식 Docker 이미지를 그대로 사용했다. DATABASE_URL만 Secret에서 주입하면 알아서 스키마 마이그레이션까지 해준다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: umami
  namespace: umami
spec:
  replicas: 1
  template:
    spec:
      nodeSelector:
        node-role: infra
      containers:
        - name: umami
          image: ghcr.io/umami-software/umami:postgresql-latest
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: umami-db-secret
                  key: DATABASE_URL
          resources:
            requests:
              memory: "256Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"

ArgoCD GitOps

배포는 ArgoCD Application으로 관리한다. homelab Git 레포의 해당 경로를 가리키고, auto-sync + selfHeal을 켜둬서 YAML을 수정하고 push하면 자동으로 반영된다. kubectl apply를 직접 칠 일이 없다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: umami
  namespace: argocd
spec:
  project: company-internal
  source:
    repoURL: https://gitea.xssh.org/homelab/homelab.git
    targetRevision: master
    path: k8s-company-internal/services/umami
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Traefik으로 HTTPS 노출

클러스터에 이미 Traefik이 Ingress Controller로 돌고 있어서, IngressRoute CRD로 umami.xssh.org를 Umami 서비스에 연결했다. HTTP로 들어오면 HTTPS로 리다이렉트하고, TLS는 cert-manager가 발급한 와일드카드 인증서를 쓴다.

배포 후 umami.xssh.org에 접속하면 Umami 로그인 화면이 뜬다. 기본 계정은 admin / umami인데, 첫 로그인 후 반드시 비밀번호를 변경해야 한다.

삽질: NFS 볼륨을 괜히 넣었다

처음 배포할 때 습관적으로 NFS 공유 볼륨을 마운트했다. 다른 서비스에서 쓰던 패턴을 그대로 복붙한 거다. 근데 Umami는 모든 데이터를 PostgreSQL에만 저장한다. 파일 시스템에 쓰는 게 없다. NFS가 아무 역할을 못 하고 있다는 걸 깨닫고 바로 다음 커밋에서 제거했다.

교훈: 새 서비스를 배포할 때 기존 템플릿을 복붙하면 불필요한 리소스가 따라온다. 그 서비스가 실제로 뭘 필요로 하는지 먼저 확인하자.

삽질: Cloudflare Tunnel 뒤에서 트래킹이 안 된다

이게 진짜 삽질이었다. Umami를 배포하고 museck.com에 트래킹 스크립트를 넣었는데, 대시보드에 아무 데이터도 안 찍혔다.

원인은 브라우저의 Private Network Access 정책이다. museck.com은 Cloudflare Tunnel을 통해 서빙되는데, 이 페이지에서 내부 네트워크(Tailscale)에 있는 umami.xssh.org로 직접 요청을 보내면 브라우저가 차단한다. 공용 인터넷 → 사설 네트워크 요청을 막는 보안 정책이다.

해결 방법은 Next.js rewrites로 트래킹 스크립트와 API 호출을 프록시하는 거다. 브라우저 입장에서는 같은 도메인(museck.com)으로 요청을 보내는 것이므로 Private Network Access 정책에 걸리지 않는다.

// next.config.mjs
async rewrites() {
  return [
    {
      source: '/stats/:match*',
      destination: 'https://umami.xssh.org/:match*',
    },
  ]
}

그러면 트래킹 스크립트 태그도 umami.xssh.org 대신 museck.com/stats/script.js를 가리키게 바꿔야 한다. 이렇게 하면 브라우저 → museck.com(Cloudflare) → 서버 사이드에서 umami.xssh.org로 프록시되므로 Private Network Access 정책을 우회할 수 있다.

리소스 사용량

실제 운영 중인 리소스 사용량을 보면 Umami가 얼마나 가벼운지 알 수 있다.

  • PostgreSQL: requests 128Mi / 100m CPU, limits 256Mi
  • Umami 앱: requests 256Mi / 100m CPU, limits 512Mi / 500m CPU
  • 디스크: PostgreSQL PVC 1~2Gi면 소규모 사이트에서 수년간 충분

합산해도 메모리 384Mi requests에 CPU 200m. 클라우드에서 가장 저렴한 VPS 하나로도 넉넉하다. 홈랩이라면 다른 서비스 옆에 슬쩍 끼워넣으면 된다.

정리

Umami를 K8s에 올리는 것 자체는 30분이면 끝난다. PostgreSQL + Umami 두 개 Deployment, PVC 하나, IngressRoute 하나. YAML 복붙하고 Secret만 바꾸면 된다.

진짜 시간을 잡아먹은 건 Cloudflare Tunnel 환경에서 트래킹이 안 되는 문제였다. Private Network Access 정책이라는 걸 알기까지가 오래 걸렸고, Next.js rewrites로 프록시하는 방법을 찾은 뒤에는 금방 해결됐다.

GA 대신 Umami를 셀프호스팅하면 쿠키 동의 배너를 안 달아도 되고, 사용자 데이터가 내 서버에만 남는다. 개인 블로그나 소규모 서비스라면 이 정도면 충분하다.

자주 묻는 질문

Umami Analytics는 Google Analytics와 뭐가 다른가요?
Umami는 쿠키를 사용하지 않고, 수집 데이터가 자체 서버에 저장되므로 GDPR 동의 배너가 필요 없습니다. 페이지뷰, 방문자, 리퍼러 등 핵심 지표는 동일하게 수집하면서도 트래킹 스크립트가 2KB 미만으로 가볍습니다.
Umami 셀프호스팅에 서버 사양이 얼마나 필요한가요?
PostgreSQL 128~256Mi, Umami 앱 256~512Mi 정도면 충분합니다. 소규모 사이트 기준으로 가장 저사양 VPS에서도 돌릴 수 있을 정도로 가볍습니다.
Cloudflare 뒤에서 Umami 트래킹이 안 되는 이유는 뭔가요?
브라우저의 Private Network Access 정책 때문입니다. Cloudflare Tunnel을 통해 서빙되는 사이트에서 내부 네트워크의 Umami로 직접 요청을 보내면 차단됩니다. Next.js rewrites로 트래킹 스크립트를 프록시하면 해결됩니다.
홈랩 삽질기(14/19)
Prev

ntfy + ArgoCD 배포 알림: 글로벌 구독의 함정과 per-app 알림

Next

GPU 하나를 여러 서비스가 나눠 쓸 때, ComfyUI 온디맨드 관리