[카테고리:] 홈서버구축

  • Rocky Linux 9 Docker 볼륨 Restic 백업 자동화 방법

    Rocky Linux 9 Docker 볼륨 Restic 백업 자동화 방법

    서버를 오래 켜두다 보면 업데이트만큼 신경 쓰이는 것이 백업입니다. Oracle Cloud Free Tier ARM 인스턴스에 Rocky Linux 9.x를 올리고 Docker Compose v2로 서비스를 돌려보니, 컨테이너는 다시 만들 수 있어도 볼륨 안의 데이터는 한 번 잃으면 복구가 쉽지 않았습니다.

    홈서버 백업 구성 참고 이미지

    Docker 홈서버에서 백업할 것

    Docker 기반 홈서버에서 지켜야 할 대상은 크게 컨테이너 볼륨, Compose 설정 파일, 데이터베이스 덤프 세 가지입니다.

    컨테이너 이미지는 다시 받을 수 있고 컨테이너도 다시 생성할 수 있습니다. 하지만 /var/lib/docker/volumes 안의 앱 데이터나 직접 바인드 마운트한 ./data, ./config 폴더는 서버의 실제 상태에 가깝습니다.

    특히 홈서버 서비스 중에는 SQLite, PostgreSQL, MariaDB처럼 데이터베이스를 함께 쓰는 경우가 많습니다. 이런 서비스는 폴더를 그대로 복사하기보다, 가능하면 백업 직전에 덤프 파일을 따로 만든 뒤 함께 백업하는 방식이 더 안전합니다.

    Restic을 선택한 이유

    Restic은 단일 바이너리로 동작하고, 암호화를 기본으로 지원합니다. S3 호환 저장소, 로컬 디스크, SFTP 같은 백엔드도 폭넓게 사용할 수 있습니다. 개인 홈서버에서는 설정이 과하게 무겁지 않고 복구 명령이 명확한 점이 장점이었습니다.

    순위 도구 핵심 강점 총평 평점
    1 Restic 암호화 기본, S3 호환 저장소 지원, 명령 구조 단순 홈서버 자동 백업용으로 균형이 좋음 4.7
    2 BorgBackup 중복 제거와 압축 효율이 좋음 리눅스 서버 간 백업에는 강하지만 S3 연동은 별도 구성이 필요함 4.4
    3 rsync 가볍고 직관적 암호화, 스냅샷, 보존 정책은 직접 설계해야 함 3.8

    Rocky Linux 9.x 기준으로는 EPEL을 켜고 설치하는 흐름이 간단합니다.

    bash

    sudo dnf install -y epel-release
    sudo dnf install -y restic
    restic version
    

    저장소 초기화와 암호 관리

    Restic 저장소는 처음 한 번 초기화해야 합니다. 아래 예시는 로컬 백업 디스크 기준입니다. MinIO 같은 S3 호환 저장소를 쓰더라도 전체 흐름은 비슷합니다.

    bash

    sudo mkdir -p /backup/restic-repo
    sudo chmod 700 /backup/restic-repo
    

    비밀번호는 셸 히스토리에 남기지 않는 편이 좋습니다. 저는 전용 파일을 만들고 권한을 좁혀두는 방식을 씁니다.

    bash

    sudo mkdir -p /etc/restic
    sudo nano /etc/restic/password
    sudo chmod 600 /etc/restic/password
    

    초기화는 다음처럼 진행합니다.

    bash

    sudo restic \
      -r /backup/restic-repo \
      --password-file /etc/restic/password \
      init
    

    S3 호환 저장소를 쓴다면 환경 변수 파일을 따로 둡니다.

    bash

    sudo nano /etc/restic/env
    sudo chmod 600 /etc/restic/env
    
    bash

    export AWS_ACCESS_KEY_ID="minio-access-key"
    export AWS_SECRET_ACCESS_KEY="minio-secret-key"
    export RESTIC_REPOSITORY="s3:http://minio.example.com:9000/restic/home-server"
    export RESTIC_PASSWORD_FILE="/etc/restic/password"
    

    이 경우 초기화는 아래처럼 실행합니다.

    bash

    sudo bash -c 'source /etc/restic/env && restic init'
    
    서버 백업 저장소 구성 참고 이미지

    백업 대상 디렉터리 정리

    Compose 프로젝트를 /opt/stacks 아래에 모아두면 백업 범위를 잡기 쉽습니다.

    bash

    /opt/stacks/
      nginx-proxy/
      gitea/
      vaultwarden/
      postgres/
    

    Restic 백업 대상은 처음부터 넓게 잡기보다 복구에 필요한 최소 단위로 정리하는 편이 좋았습니다.

    bash

    sudo mkdir -p /opt/backups/db
    

    백업 스크립트는 /usr/local/sbin/restic-docker-backup.sh 같은 이름으로 두면 관리하기 편합니다.

    bash

    #!/usr/bin/env bash
    set -euo pipefail
    
    source /etc/restic/env
    
    BACKUP_DATE="$(date +%F_%H-%M-%S)"
    DB_BACKUP_DIR="/opt/backups/db"
    
    mkdir -p "$DB_BACKUP_DIR"
    
    docker exec postgres pg_dumpall -U postgres > "$DB_BACKUP_DIR/postgres-$BACKUP_DATE.sql"
    
    restic backup \
      /opt/stacks \
      /opt/backups/db \
      /etc/restic \
      --exclude '/opt/stacks/**/cache' \
      --exclude '/opt/stacks/**/tmp'
    
    restic forget \
      --keep-daily 7 \
      --keep-weekly 4 \
      --keep-monthly 6 \
      --prune
    

    실행 권한을 부여합니다.

    bash

    sudo chmod 700 /usr/local/sbin/restic-docker-backup.sh
    

    여기서 /etc/restic까지 백업 대상에 넣을지는 선택이 필요합니다. 저장소 비밀번호 파일까지 같이 백업하면 편하지만, 그만큼 관리 포인트도 늘어납니다. 저는 비밀번호는 별도 관리 저장소에 두고, 서버 백업에서는 제외하는 쪽을 더 선호합니다.

    컨테이너를 멈춰야 하는 경우

    모든 Docker 볼륨을 같은 방식으로 백업하면 복구 시점에 차이가 생깁니다. 파일만 저장하는 서비스는 대체로 무중단 백업이 가능하지만, 데이터베이스가 계속 쓰고 있는 파일은 일관성 문제가 생길 수 있습니다.

    순위 방식 핵심 강점 총평 평점
    1 DB 덤프 후 무중단 백업 서비스 중단이 거의 없음 PostgreSQL, MariaDB 운영에 가장 현실적인 방식 4.8
    2 컨테이너 일시 중지 후 볼륨 백업 파일 상태를 단순하게 맞출 수 있음 개인 서비스라면 새벽 시간대에 적용하기 좋음 4.2
    3 실행 중인 볼륨 그대로 백업 구성이 가장 간단함 캐시나 첨부파일 위주 서비스에는 가능하지만 DB에는 주의가 필요함 3.3

    예를 들어 Vaultwarden처럼 SQLite를 쓰는 서비스는 백업 시점에 쓰기 작업이 겹칠 수 있습니다. 사용량이 적은 개인 서버라면 잠깐 멈추고 백업하는 편이 더 낫습니다.

    bash

    cd /opt/stacks/vaultwarden
    docker compose stop
    sudo bash -c 'source /etc/restic/env && restic backup /opt/stacks/vaultwarden'
    docker compose start
    

    반대로 사진, 문서, 설정 파일 중심의 서비스는 Restic으로 디렉터리를 그대로 묶어도 충분한 경우가 많습니다. 다만 데이터베이스는 덤프 파일을 남긴 뒤 백업한다는 기준은 정해두는 편이 좋습니다.

    Docker 볼륨 백업 흐름 참고 이미지

    systemd timer로 자동 실행하기

    Rocky Linux 9.x에서는 cron도 쓸 수 있지만, 로그 확인과 실행 상태 관리까지 생각하면 systemd timer가 더 깔끔합니다.

    서비스 파일을 만듭니다.

    bash

    sudo nano /etc/systemd/system/restic-docker-backup.service
    
    ini

    [Unit]
    Description=Restic Docker volume backup
    
    [Service]
    Type=oneshot
    ExecStart=/usr/local/sbin/restic-docker-backup.sh
    

    타이머 파일도 추가합니다.

    bash

    sudo nano /etc/systemd/system/restic-docker-backup.timer
    
    ini

    [Unit]
    Description=Run Restic Docker backup every day
    
    [Timer]
    OnCalendar=*-*-* 03:30:00
    Persistent=true
    Unit=restic-docker-backup.service
    
    [Install]
    WantedBy=timers.target
    

    활성화는 아래처럼 합니다.

    bash

    sudo systemctl daemon-reload
    sudo systemctl enable --now restic-docker-backup.timer
    systemctl list-timers restic-docker-backup.timer
    

    로그는 이 명령으로 확인합니다.

    bash

    journalctl -u restic-docker-backup.service -n 100 --no-pager
    

    cron으로 단순하게 처리하고 싶다면 아래 방식도 가능합니다.

    bash

    sudo crontab -e
    
    cron

    30 3 * * * /usr/local/sbin/restic-docker-backup.sh >> /var/log/restic-docker-backup.log 2>&1
    

    cron은 단순하지만 실패 원인을 좁혀볼 때 systemd보다 손이 더 갑니다. 그래서 저는 홈서버 백업 자동화는 systemd timer 쪽이 관리하기 편하다고 느꼈습니다.

    복구 테스트까지 확인하기

    백업 명령이 성공했다고 끝내면 불안합니다. 실제로는 스냅샷 목록을 보고, 일부 파일을 임시 경로에 복원해보는 과정까지 확인해야 합니다.

    bash

    sudo bash -c 'source /etc/restic/env && restic snapshots'
    

    최신 스냅샷의 내용을 확인합니다.

    bash

    sudo bash -c 'source /etc/restic/env && restic ls latest'
    

    임시 디렉터리에 복구합니다.

    bash

    sudo mkdir -p /tmp/restic-restore-test
    sudo bash -c 'source /etc/restic/env && restic restore latest --target /tmp/restic-restore-test'
    

    DB 덤프 파일이 들어왔는지도 확인합니다.

    bash

    ls -lh /tmp/restic-restore-test/opt/backups/db
    

    복구 테스트가 끝나면 임시 파일을 정리합니다.

    bash

    sudo rm -rf /tmp/restic-restore-test
    
    백업 복구 테스트 참고 이미지

    운영 기준 정리

    Oracle Cloud Free Tier ARM에 Rocky Linux 9.x, Docker Compose v2 조합이라면 백업 자동화 자체는 크게 복잡하지 않습니다. 중요한 것은 도구보다 기준이었습니다. 무엇을 백업할지, 언제 멈출지, 실제로 복구되는지를 정해두면 Restic은 그 흐름을 안정적으로 받쳐줍니다.

    저는 Compose 파일과 볼륨 데이터, DB 덤프를 함께 묶고 systemd timer로 매일 새벽 실행합니다. 그리고 가끔 restic restore로 직접 꺼내봅니다. 홈서버는 결국 혼자 관리하는 작은 운영 환경이라서, 나중의 제가 다시 봐도 이해할 수 있는 단순한 구성이 가장 오래 갑니다.

  • Uptime Kuma 홈서버 모니터링 구성과 장애 알림 설정

    직접 구성해보니 모니터링에서 가장 크게 체감한 부분은 “서비스가 죽었다”는 사실보다 언제부터 상태가 흔들렸는지였습니다. 홈서버는 하루 종일 들여다보는 장비가 아니다 보니, 장애를 늦게 발견하면 원인을 거슬러 올라가는 데 더 많은 시간이 걸립니다.

    Rocky Linux 9.x에서 Uptime Kuma 올리기

    Rocky Linux 9.x 서버에 Docker와 Compose v2가 이미 설치되어 있다는 전제로 구성했습니다. Uptime Kuma는 별도 데이터베이스 없이 컨테이너 하나로 시작할 수 있어 홈서버 모니터링용으로 부담이 적었습니다.

    저는 설정 파일과 데이터를 분리하기 위해 /opt/uptime-kuma 아래에 Compose 파일과 볼륨 디렉터리를 두었습니다. 핵심은 컨테이너가 재시작되더라도 모니터링 이력과 알림 설정이 사라지지 않도록 볼륨 경로를 고정하는 것입니다.

    bash

    sudo mkdir -p /opt/uptime-kuma/data
    cd /opt/uptime-kuma
    
    yaml

    services:
      uptime-kuma:
        image: louislam/uptime-kuma:1
        container_name: uptime-kuma
        ports:
          - "3001:3001"
        volumes:
          - ./data:/app/data
        restart: unless-stopped
    

    실행은 Compose v2 기준으로 아래처럼 하면 됩니다.

    bash

    docker compose up -d
    

    컨테이너가 올라온 뒤에는 브라우저에서 http://서버IP:3001로 접속해 관리자 계정을 만들면 됩니다. 내부망에서만 사용할 계획이라면 공유기 포트포워딩은 열지 않는 편이 낫습니다.

    재시작 정책과 볼륨을 먼저 챙긴 이유

    Uptime Kuma는 모니터링 도구라서 다른 서비스보다 더 꾸준히 살아 있어야 합니다. 그래서 restart: unless-stopped를 넣어 Docker 데몬 재시작이나 서버 재부팅 이후에도 자동으로 올라오게 했습니다.

    always를 쓰는 경우도 있지만, 제가 쓰는 홈서버에서는 관리자가 명시적으로 컨테이너를 내렸다면 그 상태를 유지하는 쪽이 더 자연스러웠습니다. 그래서 unless-stopped 정책이 운영 방식에 더 잘 맞았습니다.

    항목 장점 단점
    restart: unless-stopped 서버 재부팅 후 자동 복구되고, 사용자가 직접 내린 상태는 유지됩니다. 홈서버 운영에 가장 무난한 선택입니다. 장애 테스트 중 컨테이너를 내려둔 사실을 잊으면 계속 꺼진 상태로 남을 수 있습니다.
    restart: always Docker가 살아나는 순간 컨테이너도 다시 올라옵니다. 의도적으로 중지한 컨테이너도 다시 살아날 수 있어 점검 중에는 불편할 수 있습니다.
    재시작 정책 없음 동작이 단순하고 예측하기 쉽습니다. 서버 재부팅 후 모니터링이 꺼진 채 방치될 가능성이 큽니다.

    볼륨은 ./data:/app/data처럼 잡아두면 /opt/uptime-kuma/data에 설정과 이력이 남습니다. 나중에 서버를 옮기거나 백업할 때도 이 디렉터리만 챙기면 복구가 비교적 간단합니다.

    서비스별로 다른 방식으로 감시하기

    홈서버에 여러 컨테이너를 올려두면 서비스마다 확인 방식이 조금씩 달라집니다. HTTP만 확인하면 놓치는 부분이 있고, Ping만 보면 애플리케이션이 실제로 응답하는지 알기 어렵습니다.

    MinIO처럼 포트 응답이 중요한 서비스는 TCP와 HTTP를 나눠서 보는 편이 좋았습니다. Immich는 웹 화면이나 API 응답 여부를 보는 쪽이 실용적이고, Open WebUI는 로그인 화면이 뜨는지만 확인해도 초기 장애 감지에는 충분했습니다.

    서비스 확인 방식 한계
    MinIO TCP 9000, 콘솔 HTTP 응답을 나눠 보면 스토리지 접근 문제를 빨리 알 수 있습니다. 인증이 필요한 API까지 깊게 확인하려면 별도 엔드포인트 설계가 필요합니다.
    Immich HTTP 상태 확인으로 웹 서비스 장애를 감지하기 좋습니다. 백그라운드 작업 큐나 DB 상태까지는 기본 HTTP 체크만으로 부족할 수 있습니다.
    Open WebUI 웹 UI 응답 여부를 간단히 확인할 수 있어 운영 부담이 적습니다. 모델 서버나 백엔드 연결 문제는 별도 체크가 필요할 수 있습니다.

    Uptime Kuma에서 모니터를 추가할 때는 서비스 성격에 맞춰 이렇게 나눴습니다.

    text

    MinIO API        TCP   192.168.0.10:9000
    MinIO Console    HTTP  http://192.168.0.10:9001
    Immich Web       HTTP  http://192.168.0.10:2283
    Open WebUI       HTTP  http://192.168.0.10:3000
    Home Server      Ping  192.168.0.10
    

    HTTP 체크는 상태 코드가 200번대로 돌아오는지 보는 방식이라 가장 직관적입니다. TCP는 서비스 포트가 열려 있는지 확인하는 데 좋고, Ping은 서버 자체가 네트워크에서 보이는지 확인하는 용도로 두면 됩니다.

    Discord Webhook으로 알림 보내기

    알림은 Telegram도 많이 쓰지만, 저는 홈서버 알림을 한 채널에 모아두기 쉬운 Discord Webhook 방식으로 설정했습니다. 이미 개인 서버나 운영 채널을 쓰고 있다면 별도 봇을 만들지 않아도 되는 점이 편합니다.

    Discord에서 채널 설정으로 들어가 Webhook을 만들고 URL을 복사합니다. 그다음 Uptime Kuma의 Settings > Notifications에서 Discord를 선택한 뒤 Webhook URL을 붙여 넣으면 됩니다.

    설정 후에는 바로 저장하고 끝내지 말고 Test 버튼으로 실제 알림이 오는지 확인해야 합니다. 이 단계에서 알림이 오지 않으면 장애 상황에서도 조용할 수밖에 없습니다. 채널 권한, Webhook URL 오타, 방화벽 설정을 먼저 확인하는 편이 좋습니다.

    알림 문구는 길게 만들 필요가 없었습니다. 서비스명, 상태, 발생 시각 정도만 있어도 휴대폰에서 보고 바로 판단할 수 있습니다.

    text

    [DOWN] Immich Web
    URL: http://192.168.0.10:2283
    Time: 2026-07-20 01:12:30
    

    내부망 전용과 Cloudflare Tunnel 비교

    Uptime Kuma를 꼭 외부에 공개할 필요는 없습니다. 집 안이나 VPN 안에서만 확인한다면 내부 IP로 접근하는 구성이 가장 단순합니다. 외부에서도 상태를 보고 싶다면 Cloudflare Tunnel 뒤에 두는 방식도 선택할 수 있습니다.

    구성 장점 단점
    내부망 전용 외부 노출이 거의 없고 구성이 단순합니다. 홈서버 기본값으로 두기 좋습니다. 밖에서는 VPN 없이 접속하기 어렵습니다.
    Cloudflare Tunnel 포트포워딩 없이 외부 접속 주소를 만들 수 있습니다. 계정, 터널, 접근 정책을 추가로 관리해야 합니다. 설정이 느슨하면 모니터링 화면이 노출될 수 있습니다.

    내부망 전용이면 Compose의 포트 바인딩을 조금 더 제한할 수 있습니다.

    yaml

    ports:
      - "192.168.0.10:3001:3001"
    

    이렇게 하면 해당 내부 IP에서만 포트를 열도록 구성할 수 있습니다. 다만 서버 네트워크 환경에 따라 IP가 바뀌면 접속이 안 될 수 있으니, 홈서버에는 고정 IP를 주는 편이 관리하기 쉽습니다.

    Cloudflare Tunnel 뒤에 둘 경우에는 Uptime Kuma 자체 로그인만 믿기보다 Cloudflare Access 같은 접근 제어를 함께 두는 쪽이 낫습니다. 모니터링 화면에는 서비스 주소와 장애 이력이 들어가기 때문에 생각보다 민감한 정보가 많습니다.

    이력으로 장애 시작 시점 찾기

    Uptime Kuma를 며칠만 켜둬도 의외로 패턴이 보입니다. 특정 시간대에 Immich가 잠깐 느려지거나, MinIO 콘솔만 간헐적으로 끊기는 식입니다.

    이럴 때 “지금 죽었나?”보다 언제부터 불안정했나를 보는 게 더 중요했습니다. 로그를 뒤져볼 기준 시간이 생기기 때문입니다. 예를 들어 새벽 2시부터 HTTP 체크가 흔들렸다면, 그 시간대의 백업 작업, 컨테이너 업데이트, 스토리지 I/O를 같이 보면 됩니다.

    모니터링 간격은 너무 촘촘하게 잡지 않았습니다. 개인 홈서버 기준으로는 30초에서 60초 정도면 충분했습니다. 외부 서비스처럼 SLA를 따지는 환경이 아니라면, 감지 속도보다 알림 피로도를 줄이는 쪽이 오래 갑니다.

    Uptime Kuma 자체 장애에 대비하기

    모니터링 도구도 결국 하나의 컨테이너입니다. 그래서 Uptime Kuma만 믿고 끝내기보다는 최소한의 백업과 점검 루틴을 만들어두는 게 좋았습니다.

    • /opt/uptime-kuma/data 디렉터리 주기 백업
    • Compose 파일을 별도 Git 저장소나 NAS에 보관
    • 서버 재부팅 후 docker compose ps로 상태 확인
    • Discord 테스트 알림을 한 달에 한 번 정도 직접 실행
    • Uptime Kuma 컨테이너 로그 확인
    bash

    cd /opt/uptime-kuma
    docker compose ps
    docker logs --tail=100 uptime-kuma
    

    백업은 거창하게 시작하지 않아도 됩니다. 중요한 건 설정과 이력을 잃지 않는 것입니다. 특히 알림 설정을 다시 만드는 일이 은근히 번거로워서, 데이터 디렉터리 백업은 처음 구성할 때 같이 잡아두는 편이 낫습니다.

    홈서버 모니터링은 장애를 없애는 도구라기보다 장애를 늦게 발견하는 시간을 줄이는 장치에 가깝습니다. Uptime Kuma를 붙여두니 서비스별 상태를 한 화면에서 볼 수 있었고, 무엇보다 장애가 시작된 시점을 남길 수 있어 운영할 때 꽤 쓸모가 있었습니다.

  • Rocky Linux 9 홈서버 MinIO 개인 S3 스토리지 구축법

    Rocky Linux 9.x에서 Docker Compose v2로 구성하는 기준입니다. MinIO는 RELEASE.xxxx 계열을 전제로 하며, 실제 운영에서는 공식 이미지의 특정 릴리스 태그를 고정해두는 편이 좋습니다.

    MinIO는 S3 API와 호환되는 오브젝트 스토리지입니다. AWS S3처럼 버킷을 만들고 접근 키로 파일을 올리고 내려받는 저장소를 홈서버 안에 직접 구성하는 방식입니다.

    홈서버에서 MinIO를 쓰는 이유

    홈서버에 MinIO를 올리면 일반 파일 공유와는 쓰임새가 조금 다릅니다. 네트워크 드라이브처럼 사람이 직접 폴더를 여는 목적보다는, 앱이나 백업 도구가 S3 저장소처럼 접근할 수 있는 공간을 만드는 데 가깝습니다.

    사진 원본을 장기 보관하거나, 서버 설정 백업을 쌓아두거나, 개인 앱의 첨부파일 저장소로 붙이기 좋습니다. S3 호환 저장소를 지원하는 백업 도구와 셀프호스팅 앱이 많기 때문에, 경우에 따라 NAS 공유 폴더보다 연결하기 편합니다.

    방식 주요 용도 강점 적합한 상황
    MinIO S3 호환 오브젝트 스토리지 앱·백업 도구와 연결하기 쉬움 백업 파일, 사진 원본, 앱 데이터 저장
    Samba 공유 일반 파일 공유 윈도우 탐색기 접근이 쉬움 사람이 직접 파일을 옮기는 용도
    NFS 리눅스 서버 간 파일 공유 서버 간 마운트에 익숙함 내부 리눅스 서버용 공유 저장소

    데이터 디렉터리 먼저 정하기

    MinIO는 컨테이너를 지워도 데이터가 남아야 하므로, 먼저 호스트에 데이터 디렉터리를 만들어둡니다. 여기서는 /srv/minio/data를 사용합니다.

    bash

    sudo mkdir -p /srv/minio/data
    sudo chown -R 1000:1000 /srv/minio
    

    권한은 운영 방식에 따라 달라질 수 있습니다. 다만 홈서버에서는 처음부터 데이터 위치를 분명히 잡아두는 편이 좋습니다. 나중에 디스크를 추가하거나 백업 경로를 정리할 때 MinIO 데이터가 어디에 있는지 바로 확인할 수 있습니다.

    Docker Compose 파일 구성

    작업 디렉터리를 만들고 compose.yml을 작성합니다.

    bash

    mkdir -p ~/minio
    cd ~/minio
    nano compose.yml
    

    아래는 기본 구성입니다. 관리자 계정과 비밀번호는 예시 그대로 쓰지 말고 반드시 변경해야 합니다.

    yaml

    services:
      minio:
        image: quay.io/minio/minio:RELEASE.xxxx
        container_name: minio
        restart: unless-stopped
        command: server /data --console-address ":9001"
        ports:
          - "9000:9000"
          - "9001:9001"
        environment:
          MINIO_ROOT_USER: "minioadmin_change_me"
          MINIO_ROOT_PASSWORD: "change_this_password"
        volumes:
          - /srv/minio/data:/data
    

    9000은 S3 API 포트이고, 9001은 웹 콘솔 포트입니다. 내부망에서 먼저 테스트한다면 이 구성으로 시작할 수 있습니다.

    bash

    docker compose up -d
    docker compose ps
    

    브라우저에서 http://서버IP:9001로 접속하면 MinIO 콘솔이 열립니다. MINIO_ROOT_USER, MINIO_ROOT_PASSWORD에 넣은 값으로 로그인합니다.

    버킷 생성과 접근 키 발급

    콘솔에 접속하면 먼저 버킷을 만듭니다. 사진 원본용이면 photos, 백업 파일용이면 backup, 앱 첨부파일용이면 app-data처럼 목적이 드러나는 이름을 쓰는 편이 관리하기 쉽습니다.

    버킷을 만든 뒤에는 루트 계정을 계속 쓰지 말고 별도 접근 키를 발급하는 흐름이 좋습니다. 콘솔의 Access Keys 메뉴에서 새 키를 만들고, 발급된 Access Key와 Secret Key를 백업 도구나 앱 설정에 넣으면 됩니다.

    권한은 처음부터 넓게 주기보다 용도별로 나누는 편이 낫습니다. 백업 도구는 backup 버킷만 읽고 쓰게 하고, 사진 업로드 앱은 photos 버킷만 쓰게 하는 식입니다. 홈서버라도 루트 계정 상시 사용은 피하는 편이 운영하기 좋습니다.

    외부 공개 전 내부망 테스트

    MinIO를 외부에 바로 여는 것은 신중해야 합니다. 특히 9001 콘솔 포트를 인터넷에 그대로 노출하면 관리자 화면이 외부에 보이는 구조가 됩니다.

    처음에는 공유기 내부망이나 VPN 안에서만 접속해보는 것을 권합니다. 외부 접속이 꼭 필요하다면 리버스 프록시, TLS 인증서, 접근 제한, 터널 구성을 먼저 정리해야 합니다. Cloudflare Tunnel이나 Nginx Proxy Manager를 붙일 수는 있지만, 그 전에 콘솔 포트와 API 포트를 어떻게 분리할지부터 정하는 게 좋습니다.

    방화벽을 쓰고 있다면 내부망 테스트 기준으로 필요한 포트만 열어둡니다.

    bash

    sudo firewall-cmd --add-port=9000/tcp --permanent
    sudo firewall-cmd --add-port=9001/tcp --permanent
    sudo firewall-cmd --reload
    

    공개 서버라면 이 명령을 그대로 적용하기 전에 접근 범위를 먼저 확인해야 합니다.

    백업 저장소로 쓸 때의 메모

    MinIO는 백업 대상이라기보다 백업 파일을 받아주는 저장소로 볼 때 장점이 분명합니다. 여러 앱이 S3 방식으로 연결할 수 있고, 버킷 단위로 데이터를 나누기 쉬우며, 나중에 다른 S3 호환 도구로 옮기기도 비교적 수월합니다.

    다만 디스크 하나에 MinIO만 올려두면 그 디스크가 고장 났을 때 백업 저장소도 같이 사라집니다. 중요한 자료라면 별도 디스크, 다른 서버, 외장 저장장치, 클라우드 중 하나로 한 번 더 복제하는 구조가 필요합니다.

    홈서버에서 MinIO는 파일을 직접 열어보는 공유 폴더라기보다, 백업 도구와 개인 앱이 안정적으로 데이터를 밀어 넣는 개인 S3 저장소로 보는 편이 맞습니다. 이렇게 생각하면 구성 방향도 훨씬 분명해집니다.

  • Cloudflare Tunnel 홈서버 외부접속 Rocky Linux 9 설정법

    Cloudflare Tunnel 홈서버 외부접속 Rocky Linux 9 설정법

    집 안에 둔 서버를 밖에서 접속하려고 하면 늘 한 지점에서 걸렸습니다. 공유기 포트포워딩은 설정 자체는 어렵지 않지만, 한 번 열어두면 계속 신경이 쓰입니다. DDNS도 공인 IP 여부나 ISP의 CGNAT 환경에 따라 생각보다 깔끔하게 끝나지 않을 때가 있었습니다.

    홈서버 네트워크 구성 참고 이미지

    포트포워딩을 열지 않기로 한 이유

    이번 구성은 Rocky Linux 9 서버에서 Cloudflare Tunnel로 외부 접속을 붙이는 방식입니다. 서버가 Cloudflare 쪽으로 먼저 아웃바운드 연결을 만들고, 외부 요청은 그 터널을 통해 내부 서비스로 전달됩니다.

    이 구조에서는 공유기에서 80, 443 같은 인바운드 포트를 열 필요가 없습니다. firewalld에도 웹 서비스 포트를 추가하지 않았습니다. SSH는 기존처럼 관리용으로만 제한해두는 쪽이 낫다고 봤고, 이 방향은 예전에 정리한 Rocky Linux 9 앵커글이나 SSH 하드닝글의 흐름과도 잘 맞았습니다.

    Cloudflare 공식 문서에서도 Tunnel은 서버에서 실행되는 cloudflared가 Cloudflare와 연결을 맺는 방식으로 설명합니다. 설치와 로컬 관리형 터널 흐름은 공식 문서를 기준으로 확인했습니다.

    • Cloudflare Tunnel 로컬 관리형 터널 문서: https://developers.cloudflare.com/tunnel/advanced/local-management/create-local-tunnel/
    • cloudflared 다운로드 문서: https://developers.cloudflare.com/tunnel/downloads/
    • Cloudflare 패키지 저장소: https://pkg.cloudflare.com/

    비슷한 선택지를 나누면 아래처럼 정리됩니다.

    방식 장점 주의할 점
    Cloudflare Tunnel 포트포워딩 없이 HTTP 서비스를 외부에 공개하기 쉽고 Access 정책을 붙일 수 있음 Cloudflare 프록시 제한을 받으며, 대용량 업로드에는 주의가 필요함
    공유기 포트포워딩 + DDNS 구조가 단순하고 직접 제어하기 쉬움 공인 IP, ISP CGNAT, 포트 노출 문제를 직접 관리해야 함
    VPN/WARP/Tailscale 계열 내부망처럼 접근할 수 있어 관리용으로 안정적 일반 웹 서비스처럼 도메인만 알려주는 공개 접근에는 별도 설계가 필요함

    cloudflared 설치는 dnf repo 방식으로

    Rocky Linux 9에서는 curl | bash 방식 대신 Cloudflare RPM 저장소를 추가한 뒤 dnf install로 설치했습니다. 홈서버처럼 오래 운영할 서버라면 패키지로 관리하는 편이 업데이트와 제거 면에서 깔끔합니다.

    서버 터미널 작업 분위기
    bash

    sudo dnf install -y dnf-plugins-core
    sudo curl -fsSL https://pkg.cloudflare.com/cloudflared.repo -o /etc/yum.repos.d/cloudflared.repo
    sudo dnf install -y cloudflared
    cloudflared --version
    

    설치 후에는 SELinux 상태부터 확인했습니다. 저는 SELinux enforcing 유지를 전제로 작업했습니다.

    bash

    getenforce
    
    text

    Enforcing
    

    firewalld도 확인했습니다. 이번 구성의 핵심은 외부에서 들어오는 HTTP/HTTPS 포트를 열지 않는 것이므로 http, https 서비스를 추가하지 않았습니다.

    bash

    sudo firewall-cmd --list-all
    
    text

    public (active)
      target: default
      services: cockpit dhcpv6-client ssh
      ports:
      protocols:
      forward: yes
      masquerade: no
    

    중요한 점은 터널용으로 firewalld 인바운드 포트를 열지 않았다는 것입니다. 서버가 밖으로 나가는 연결을 만들기 때문에 공유기 포트포워딩도 건드리지 않았습니다.

    터널 생성과 config.yml 작성

    Cloudflare 계정에 도메인이 연결되어 있다는 전제에서 진행했습니다. 먼저 브라우저 인증을 한 번 거칩니다.

    bash

    cloudflared tunnel login
    

    제 환경에서는 아래처럼 인증 URL이 출력됐고, 브라우저에서 도메인을 선택한 뒤 인증 파일이 생성됐습니다.

    text

    Please open the following URL and log in with your Cloudflare account:
    
    https://dash.cloudflare.com/argotunnel?callback=https%3A%2F%2Flogin.cloudflareaccess.org%2F...
    
    You have successfully logged in.
    If you wish to copy your credentials to a server, they have been saved to:
    /home/rocky/.cloudflared/cert.pem
    

    이어서 터널을 만들었습니다. 이름은 나중에 알아보기 쉽도록 homelab-rocky9로 잡았습니다.

    bash

    cloudflared tunnel create homelab-rocky9
    
    text

    Tunnel credentials written to /home/rocky/.cloudflared/2f7b3b7c-9a66-4c91-8b52-111111111111.json.
    Created tunnel homelab-rocky9 with id 2f7b3b7c-9a66-4c91-8b52-111111111111
    

    설정 파일은 /etc/cloudflared/config.yml에 두고 systemd에서 안정적으로 읽도록 했습니다. 아래 예시는 Immich를 http://localhost:2283으로 연결하는 형태입니다. 실제 도메인은 본인 도메인에 맞게 바꾸면 됩니다.

    bash

    sudo mkdir -p /etc/cloudflared
    sudo cp ~/.cloudflared/2f7b3b7c-9a66-4c91-8b52-111111111111.json /etc/cloudflared/
    sudo vi /etc/cloudflared/config.yml
    
    yaml

    tunnel: 2f7b3b7c-9a66-4c91-8b52-111111111111
    credentials-file: /etc/cloudflared/2f7b3b7c-9a66-4c91-8b52-111111111111.json
    
    ingress:
      - hostname: immich.example.com
        service: http://localhost:2283
      - hostname: admin.example.com
        service: http://localhost:8080
      - service: http_status:404
    

    Cloudflare 문서에서는 마지막에 catch-all 규칙을 두는 흐름을 안내합니다. 그래서 맨 아래에 http_status:404를 넣어 정의하지 않은 호스트는 막히게 했습니다.

    DNS 라우팅은 아래처럼 잡았습니다.

    bash

    cloudflared tunnel route dns homelab-rocky9 immich.example.com
    cloudflared tunnel route dns homelab-rocky9 admin.example.com
    
    text

    2026-07-17T03:41:12Z INF Added CNAME immich.example.com which will route to this tunnel tunnelID=2f7b3b7c-9a66-4c91-8b52-111111111111
    2026-07-17T03:41:18Z INF Added CNAME admin.example.com which will route to this tunnel tunnelID=2f7b3b7c-9a66-4c91-8b52-111111111111
    
    네트워크 연결 흐름 참고 이미지

    설정 검증도 한 번 해두는 편이 좋습니다.

    bash

    cloudflared tunnel ingress validate --config /etc/cloudflared/config.yml
    
    text

    Validating rules from /etc/cloudflared/config.yml
    OK
    

    systemd 등록과 재부팅 복구 확인

    수동 실행으로만 두면 서버 재부팅 때 터널이 내려갈 수 있습니다. 그래서 바로 systemd 서비스로 등록했습니다.

    bash

    sudo cloudflared service install
    sudo systemctl enable --now cloudflared
    sudo systemctl status cloudflared --no-pager
    

    정상이라면 대략 이런 로그가 보입니다.

    text

    ● cloudflared.service - cloudflared
         Loaded: loaded (/etc/systemd/system/cloudflared.service; enabled; preset: disabled)
         Active: active (running) since Fri 2026-07-17 03:48:31 UTC; 12s ago
       Main PID: 1842 (cloudflared)
          Tasks: 9
         Memory: 18.7M
            CPU: 230ms
    
    Jul 17 03:48:32 rocky9 cloudflared[1842]: INF Starting tunnel tunnelID=2f7b3b7c-9a66-4c91-8b52-111111111111
    Jul 17 03:48:33 rocky9 cloudflared[1842]: INF Registered tunnel connection connIndex=0
    Jul 17 03:48:34 rocky9 cloudflared[1842]: INF Registered tunnel connection connIndex=1
    

    재부팅 후 자동으로 복구되는지도 확인했습니다.

    bash

    sudo reboot
    

    다시 접속한 뒤 아래 명령을 실행했습니다.

    bash

    systemctl is-enabled cloudflared
    systemctl is-active cloudflared
    journalctl -u cloudflared -b --no-pager | tail -n 20
    
    text

    enabled
    active
    Jul 17 04:02:11 rocky9 cloudflared[912]: INF Starting tunnel tunnelID=2f7b3b7c-9a66-4c91-8b52-111111111111
    Jul 17 04:02:12 rocky9 cloudflared[912]: INF Registered tunnel connection connIndex=0
    Jul 17 04:02:13 rocky9 cloudflared[912]: INF Registered tunnel connection connIndex=1
    Jul 17 04:02:14 rocky9 cloudflared[912]: INF Registered tunnel connection connIndex=2
    Jul 17 04:02:15 rocky9 cloudflared[912]: INF Registered tunnel connection connIndex=3
    

    여기까지 확인해야 홈서버 운영용으로 믿고 쓸 수 있습니다. 단순히 접속이 되는지보다 서버 재부팅 후에도 터널이 자동으로 살아나는지가 더 중요했습니다.

    Immich 백업에서 만난 100MB 제한

    Cloudflare Tunnel을 붙인 뒤 가장 먼저 확인한 서비스는 Immich였습니다. 사진 백업은 잘 됐지만, 모바일에서 찍은 영상 하나가 계속 실패했습니다. 처음에는 Immich 설정이나 nginx 업로드 제한을 의심했는데, 로그를 보니 원인은 다른 쪽에 가까웠습니다.

    모바일 백업과 홈서버 연결 참고 이미지

    Immich 모바일 백업 중 대용량 영상에서 남은 로그입니다.

    text

    | immich_server | [Nest] 91  - 07/17/2026, 4:18:27 AM   ERROR [Api:AssetController] Upload failed: PayloadTooLargeException: request entity too large |
    | --- | --- |
    | immich_proxy | 172.18.0.1 - - [17/Jul/2026:04:18:27 +0000] "POST /api/assets HTTP/1.1" 413 176 "-" "Immich/1.132.3 build.197" |
    | cloudflared | 2026-07-17T04:18:27Z ERR Request failed error="HTTP status 413 Request Entity Too Large" cfRay=95ab111111111111-ICN ingressRule=0 originService=http://localhost:2283 |
    

    Cloudflare의 Request body size limit 기준으로 Free와 Pro 플랜은 요청 본문 최대 100MB입니다. 이 제한을 넘는 업로드는 413 Request Entity Too Large로 막힐 수 있습니다.

    • Cloudflare 요청 크기 제한 문서: https://developers.cloudflare.com/workers/platform/limits/

    그래서 Immich를 Cloudflare Tunnel 하나로만 쓰는 건 제 기준에서는 애매했습니다. 사진 위주라면 문제가 적지만, 요즘 휴대폰 영상은 100MB를 넘는 경우가 흔합니다.

    운영 방식 장점 주의할 점
    Immich 외부 접속을 Cloudflare Tunnel로만 사용 도메인 접속이 쉽고 포트포워딩이 필요 없음 100MB 초과 영상 업로드에서 413 오류가 날 수 있음
    집 안에서는 LAN 주소로 직접 백업 대용량 영상 업로드가 안정적이고 빠름 외부에서는 별도 접속 경로가 필요함
    외부 백업은 WARP 또는 VPN 경로로 분리 내부망 접근처럼 사용할 수 있어 대용량 업로드에 유리함 클라이언트 설정이 추가로 필요함

    현재는 집 안에서는 Immich 앱이 로컬 주소를 보도록 두고, 밖에서는 조회나 가벼운 업로드만 Tunnel을 타게 나눴습니다. 외부에서 대용량 백업까지 해야 한다면 WARP나 VPN 계열을 함께 설계하는 쪽이 현실적입니다. 이 부분은 별도 Immich글에서 모바일 백업 기준으로 더 자세히 다룰 만했습니다.

    Zero Trust Access로 관리 화면 보호하기

    Tunnel을 열었다고 해서 모든 경로를 그대로 공개하고 싶지는 않았습니다. 특히 Immich admin, 서버 관리용 대시보드, 내부 도구처럼 자체 로그인이 있더라도 앞단에 한 겹 더 두는 편이 낫습니다.

    Cloudflare Zero Trust의 Access 정책을 쓰면 특정 이메일이나 이메일 도메인 기준으로 인증 게이트를 붙일 수 있습니다. 공식 문서에서도 Access는 self-hosted application 앞에서 요청을 검사하고, 정책에 맞는 사용자만 통과시키는 방식으로 설명합니다.

    • Cloudflare Access 애플리케이션 문서: https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/
    • Cloudflare Access 정책 문서: https://developers.cloudflare.com/cloudflare-one/access-controls/policies/

    예를 들어 admin.example.com은 Access Application으로 만들고, 정책에는 제 이메일만 허용했습니다.

    보호 방식 장점 주의할 점
    Access 이메일 인증 관리 페이지 앞단에 인증 게이트를 둘 수 있음 이메일 OTP나 IdP 흐름이 한 번 추가됨
    앱 자체 로그인만 사용 구성이 단순함 앱 취약점이나 로그인 페이지 노출을 그대로 감수해야 함
    IP 제한 특정 위치에서는 깔끔함 모바일망, 외부 작업 환경에서는 관리가 번거로움

    설정 방향은 단순합니다. Zero Trust 대시보드에서 Access Application을 만들고, 도메인을 admin.example.com으로 지정한 뒤 Allow 정책에 사용할 이메일을 넣었습니다.

    text

    Application domain: admin.example.com
    Policy action: Allow
    Include selector: Emails
    Value: [email protected]
    Login method: One-time PIN
    

    주의할 점은 OTP를 아무 이메일에나 열어두지 않는 것입니다. 홈서버 관리 화면은 특히 Everyone 허용으로 두지 않는 것이 중요합니다.

    보안 접근 제어 참고 이미지

    정리

    Cloudflare Tunnel은 홈서버 외부 접속을 만들 때 꽤 실용적이었습니다. 특히 공유기 포트포워딩 없이 도메인 기반 HTTPS 접속을 붙일 수 있다는 점이 가장 컸습니다. Rocky Linux 9에서도 RPM 저장소를 추가해 dnf install cloudflared로 관리하면 설치 흐름이 깔끔했고, systemd 등록 후 재부팅 복구까지 확인하니 운영 부담도 적었습니다.

    다만 Immich처럼 대용량 업로드가 많은 서비스는 100MB 제한을 먼저 생각해야 합니다. 저는 외부 조회와 가벼운 접근은 Tunnel, 집 안 백업은 LAN 직접 연결, 관리 화면은 Zero Trust Access로 보호하는 식으로 역할을 나눴습니다.

    홈랩에서는 한 가지 도구로 전부 해결하려 하기보다 노출을 줄이고 필요한 경로만 열어두는 구성이 오래 운영하기에 편했습니다.

  • Immich 구글포토 대체 Rocky Linux 사진 백업 서버 구축

    Immich 구글포토 대체 Rocky Linux 사진 백업 서버 구축

    구글포토를 계속 쓰면서도 마음 한쪽이 불편했던 이유는 비용만은 아니었습니다. 가족 사진, 영수증, 여행 기록이 한곳에 오래 쌓이다 보니 사진 백업은 편해야 하지만, 저장 위치와 백업 방식은 직접 통제하고 싶다는 생각이 커졌습니다.

    개인 사진 백업 이미지

    구성 환경

    이번 구성은 Rocky Linux 9 + Docker Compose v2 기준입니다. Rocky Linux 기본 설정은 Rocky Linux 9 기본 설정 글을 먼저 보면 흐름이 편하고, Docker가 아직 없다면 Docker 설치글을 먼저 끝내는 편이 좋습니다.

    항목
    OS Rocky Linux 9.x
    실행 방식 Docker + Compose v2
    Immich 포트 2283/tcp
    RAM 최소 4GB, 얼굴 인식까지 고려하면 8GB 이상 권장
    사진 저장 경로 /srv/immich/library
    DB 저장 경로 /srv/immich/postgres
    공식 문서 Immich Docker Compose 안내

    Immich는 공식 문서 기준으로 docker compose 설치 흐름이 잘 정리되어 있습니다. 예전 자료에서는 pgvecto-rs DB 이미지를 쓰는 경우도 많았지만, 현재 공식 Compose 예시는 ghcr.io/immich-app/postgres 계열 이미지로 정리되어 있습니다. 이미 운영 중인 서버라면 새 Compose 파일을 그대로 덮기보다, 공식 업그레이드 문서를 먼저 확인하는 쪽이 안전합니다.

    docker-compose.yml 구성

    Immich는 기능이 많아 보여도 기본 구성은 비교적 단순합니다. 사진 업로드와 웹 화면을 담당하는 immich-server, 검색과 얼굴 인식에 쓰이는 machine-learning, 캐시용 redis, 데이터베이스가 함께 떠야 정상적으로 동작합니다.

    yaml

    name: immich
    
    services:
      immich-server:
        container_name: immich_server
        image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
        volumes:
          - ${UPLOAD_LOCATION}:/data:z
          - /etc/localtime:/etc/localtime:ro
        env_file:
          - .env
        ports:
          - "2283:2283"
        depends_on:
          - redis
          - database
        restart: always
        healthcheck:
          disable: false
    
      immich-machine-learning:
        container_name: immich_machine_learning
        image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
        volumes:
          - model-cache:/cache:z
        env_file:
          - .env
        restart: always
        healthcheck:
          disable: false
    
      redis:
        container_name: immich_redis
        image: docker.io/valkey/valkey:9
        healthcheck:
    test: redis-cli ping || exit 1
        restart: always
    
      database:
        container_name: immich_postgres
        image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
        environment:
          POSTGRES_PASSWORD: ${DB_PASSWORD}
          POSTGRES_USER: ${DB_USERNAME}
          POSTGRES_DB: ${DB_DATABASE_NAME}
          POSTGRES_INITDB_ARGS: "--data-checksums"
        volumes:
          - ${DB_DATA_LOCATION}:/var/lib/postgresql/data:z
        shm_size: 128mb
        restart: always
        healthcheck:
          disable: false
    
    volumes:
      model-cache:
    

    환경값은 .env로 분리해두는 편이 관리하기 쉽습니다. 비밀번호는 복잡하게 만들수록 좋지만, Docker 파싱 문제를 줄이려면 공식 안내처럼 영문과 숫자 조합으로 두는 쪽이 덜 번거로웠습니다.

    env

    UPLOAD_LOCATION=/srv/immich/library
    DB_DATA_LOCATION=/srv/immich/postgres
    TZ=Asia/Seoul
    IMMICH_VERSION=v3
    
    DB_PASSWORD=ChangeThisPassword1234
    DB_USERNAME=postgres
    DB_DATABASE_NAME=immich
    
    서버와 사진 백업 흐름

    SELinux와 방화벽 설정

    Rocky Linux 9에서 Immich를 올릴 때 가장 자주 막히는 부분은 SELinux입니다. Docker 볼륨에 :z 라벨을 붙이지 않거나 컨텍스트가 맞지 않으면 컨테이너는 떠도 내부에서 파일을 쓰지 못합니다.

    실제로는 이런 로그를 만나기 쉽습니다.

    bash

    Error: EACCES: permission denied, mkdir '/data/upload'
    

    DB 쪽에서는 아래처럼 보일 수 있습니다.

    bash

    initdb: error: could not access directory "/var/lib/postgresql/data": Permission denied
    

    이때 setenforce 0으로 넘기는 방식은 피하는 게 좋습니다. 계속 켜둘 홈서버라면 SELinux를 끄는 대신 컨텍스트를 맞춰서 해결하는 방식이 맞습니다.

    bash

    sudo dnf install -y policycoreutils-python-utils
    
    sudo mkdir -p /srv/immich/library /srv/immich/postgres
    
    sudo semanage fcontext -a -t container_file_t "/srv/immich(/.*)?"
    sudo restorecon -Rv /srv/immich
    

    firewalld도 Rocky Linux 기본 흐름에 맞춰 열어줍니다.

    bash

    sudo firewall-cmd --permanent --add-port=2283/tcp
    sudo firewall-cmd --reload
    sudo firewall-cmd --list-ports
    

    컨테이너 실행은 Immich Compose 파일이 있는 디렉터리에서 진행합니다.

    bash

    docker compose up -d
    docker compose ps
    

    얼굴 인식과 메모리 사용

    Immich의 장점은 사진을 단순히 저장하는 데서 끝나지 않는다는 점입니다. 업로드한 사진을 기준으로 검색, 미리보기, 얼굴 인식이 돌아가면 구글포토를 쓰던 감각과 꽤 가까워집니다.

    머신러닝과 사진 분류 작업

    다만 immich-machine-learning 컨테이너는 생각보다 메모리를 사용합니다. 사진 수가 많고 CPU만으로 인덱싱을 돌리면 초반 며칠은 서버가 계속 바쁠 수 있습니다. N100 미니PC나 ARM 서버처럼 저전력 장비에서는 업로드 직후 결과를 기대하기보다, 밤새 천천히 처리하게 두는 편이 현실적입니다.

    메모리와 CPU 사용량은 아래 명령으로 확인할 수 있습니다.

    bash

    docker stats immich_machine_learning immich_server immich_postgres
    

    CPU 인덱싱 속도는 사진 해상도와 장비에 따라 차이가 큽니다. GPU 가속을 붙이면 빨라질 수 있지만, 홈서버에서는 전력과 발열도 함께 봐야 하므로 무조건 정답이라고 보기는 어렵습니다.

    모바일 앱 백업 설정

    서버가 올라오면 브라우저에서 아래 주소로 접속합니다.

    text

    http://서버IP:2283
    

    외부에서 쓰려면 리버스 프록시와 HTTPS까지 붙이는 편이 좋습니다. 다만 집 안에서 먼저 테스트할 때는 2283 포트만으로도 충분합니다. 모바일 앱에서는 서버 URL을 넣고 로그인한 뒤 백그라운드 자동 백업을 켜면 됩니다.

    처음부터 모든 앨범을 올리기보다는 카메라 앨범부터 시작하는 편이 좋습니다. 스크린샷이나 다운로드 폴더는 나중에 필요할 때 추가해야 정리가 덜 흐트러집니다.

    스토리지 설계

    사진 백업은 처음에는 100GB도 커 보이지만, 가족 단위로 몇 년 치를 모으면 금방 단위가 바뀝니다. 휴대폰 사진에 동영상까지 섞이면 1TB는 시작점에 가깝고, 장기 보관은 4TB 이상부터 여유가 생깁니다.

    제품 장점 단점
    구글 포토 앱 완성도와 검색이 좋고 관리 부담이 거의 없습니다 용량이 늘수록 비용이 계속 붙고 데이터 위치를 직접 통제하기 어렵습니다
    Immich 셀프호스팅 원본 사진을 내 서버에 보관하고 백업 정책을 직접 정할 수 있습니다 서버 운영, 업데이트, 백업을 직접 챙겨야 합니다
    Synology NAS 저장소 관리와 디스크 교체가 편하고 가족용 공유에 강합니다 초기 비용이 있고 Docker 성능은 모델에 따라 차이가 납니다
    N100/N150 미니PC 전력 대비 성능이 좋아 Immich 서버용으로 무난합니다 디스크 베이를 따로 구성해야 해서 저장소 설계가 필요합니다
    NAS HDD 대용량을 비교적 안정적으로 운용하기 좋습니다 SSD보다 느리며 DB 저장 위치로는 적합하지 않을 수 있습니다
    홈서버 저장장치 구성

    개인적으로는 사진 원본은 NAS HDD에 두고, DB는 SSD에 두는 구성이 가장 무난해 보였습니다. Immich 공식 안내에서도 DB를 네트워크 공유에 두는 방식은 권장하지 않으니, 처음 설계할 때 저장 위치를 나눠두는 편이 좋습니다.

    마지막으로 백업은 Immich 하나로 끝내지 않는 게 안전합니다. 서버에 원본이 있고, NAS나 외장하드에 한 벌 더 있으며, 정말 중요한 사진은 외부 저장소까지 두는 3-2-1 규칙을 기준으로 잡으면 복구 선택지가 남습니다. Immich는 단순히 구글포토 화면을 대신하는 도구라기보다, 내 사진 보관 방식을 다시 정리하게 만드는 홈서버 서비스에 가깝습니다.

  • Rocky Linux 9 로컬 LLM 구축 Ollama Open WebUI Docker 설정

    Rocky Linux 9 로컬 LLM 구축 Ollama Open WebUI Docker 설정

    로컬 LLM을 집 서버에 올리는 이유

    로컬 AI를 한 번 구성해두면 생각보다 자주 쓰게 됩니다. 외부 API 키를 넣지 않아도 되고, 테스트하다가 토큰 비용이 쌓일 걱정도 없습니다. 홈서버에 붙여두기 좋은 서비스였습니다.

    로컬 서버 구축 분위기

    제가 로컬 LLM에 관심을 둔 가장 큰 이유는 프라이버시와 API 비용이었습니다.

    ChatGPT나 Claude 같은 서비스를 쓰면 편하지만, 개인 메모나 내부 문서 내용을 넣을 때는 한 번 더 생각하게 됩니다. 반대로 Ollama는 모델을 서버 안에 내려받아 실행하는 방식이라, 기본적인 질의응답은 외부 API 없이 처리할 수 있습니다.

    물론 성능은 하드웨어를 많이 탑니다. 그래도 간단한 요약, 코드 초안, 개인용 질의응답 정도라면 Rocky Linux 9 + Docker + Ollama + Open WebUI 조합이 꽤 깔끔합니다.

    공식 문서는 아래를 기준으로 확인하면 됩니다.

    구분 링크 확인할 내용
    Rocky Linux Rocky Linux 공식 사이트 Rocky Linux 9 ISO, 릴리즈 정보
    Docker Docker Engine 설치 문서 Docker 및 Compose v2 설치
    Ollama Ollama Linux 설치 Linux 설치 명령과 모델 실행
    Open WebUI Open WebUI Quick Start Docker 실행, Compose 예시

    사전 준비는 Rocky 9와 Docker부터

    이 글은 Rocky Linux 9.x에 Docker와 Compose v2가 설치된 상태를 기준으로 정리했습니다. Docker Compose는 예전의 docker-compose가 아니라 docker compose처럼 띄어 쓰는 v2 방식으로 진행합니다.

    먼저 서버에서 Docker가 정상 동작하는지 확인합니다.

    bash

    docker --version
    docker compose version
    systemctl status docker
    

    방화벽을 사용 중이라면 나중에 Open WebUI 접속용으로 3000번 포트를 열어야 합니다. Ollama의 기본 포트는 11434인데, 외부에 직접 열기보다는 Docker 네트워크 내부에서만 쓰는 편이 낫습니다.

    서버 작업 환경

    Ollama 설치와 모델 Pull

    Ollama는 호스트에 직접 설치할 수도 있고, Docker 컨테이너로 띄울 수도 있습니다. 홈서버에서 관리하기에는 Docker Compose로 함께 묶는 방식이 편했습니다.

    먼저 감을 보고 싶다면 호스트에 설치한 뒤 모델을 받아 테스트해도 됩니다.

    bash

    curl -fsSL https://ollama.com/install.sh | sh
    ollama pull llama3.2
    ollama run llama3.2
    

    모델 이름은 시점에 따라 바뀔 수 있으니, 실제 사용 전에는 Ollama 공식 라이브러리에서 한 번 확인하는 편이 좋습니다. 입문용으로는 llama3.2, 가벼운 서버라면 더 작은 모델부터 시작하는 것이 부담이 적습니다.

    모델 목록은 이렇게 확인합니다.

    bash

    ollama list
    

    Open WebUI Docker Compose 작성

    Open WebUI는 브라우저에서 ChatGPT처럼 사용할 수 있게 해주는 웹 UI입니다. 공식 문서에서도 Docker 방식을 안내하고 있고, 데이터는 볼륨에 남기는 구조가 기본입니다.

    저는 Ollama와 Open WebUI를 같은 compose 파일에 두는 구성이 가장 단순했습니다.

    yaml

    services:
      ollama:
        image: ollama/ollama:latest
        container_name: ollama
        restart: unless-stopped
        volumes:
          - ollama:/root/.ollama
        ports:
          - "127.0.0.1:11434:11434"
    
      open-webui:
        image: ghcr.io/open-webui/open-webui:main
        container_name: open-webui
        restart: unless-stopped
        depends_on:
          - ollama
        ports:
          - "3000:8080"
        environment:
          - OLLAMA_BASE_URL=http://ollama:11434
          - WEBUI_SECRET_KEY=change-this-to-a-long-random-string
        volumes:
          - open-webui:/app/backend/data
    
    volumes:
      ollama:
      open-webui:
    

    여기서 중요한 부분은 OLLAMA_BASE_URL=http://ollama:11434 입니다. 컨테이너 안에서 localhost:11434를 바라보면 Open WebUI 자기 자신을 가리키게 되어 연결이 되지 않습니다.

    실행은 compose 파일이 있는 디렉터리에서 합니다.

    bash

    docker compose up -d
    docker ps
    docker logs -f open-webui
    
    도커 컨테이너 운영 이미지

    firewalld와 SELinux에서 막히는 지점

    Rocky Linux 9에서 Ubuntu 기준 글과 차이가 나는 부분은 이 지점이었습니다. 컨테이너는 떠 있는데 브라우저 접속이 안 되거나, 볼륨을 바인드 마운트했을 때 권한 에러가 나는 경우가 있습니다.

    외부 브라우저에서 접속할 예정이라면 firewalld에 3000번 포트를 열어줍니다.

    bash

    sudo firewall-cmd --add-port=3000/tcp --permanent
    sudo firewall-cmd --reload
    sudo firewall-cmd --list-ports
    

    SELinux는 named volume을 쓰면 대체로 조용합니다. 하지만 아래처럼 호스트 디렉터리를 직접 붙이면 에러가 날 수 있습니다.

    yaml

    volumes:
      - ./open-webui-data:/app/backend/data
    

    이때 로그에는 이런 식으로 남는 경우가 있습니다.

    bash

    PermissionError: [Errno 13] Permission denied: '/app/backend/data'
    

    또는 audit 로그에서 AVC deny가 확인됩니다.

    bash

    sudo ausearch -m avc -ts recent
    

    바인드 마운트를 꼭 써야 한다면 :Z 옵션을 붙여 SELinux 컨텍스트를 컨테이너에 맞춰줍니다.

    yaml

    volumes:
      - ./open-webui-data:/app/backend/data:Z
    

    이미 만들어둔 디렉터리라면 권한도 같이 확인합니다.

    bash

    mkdir -p ./open-webui-data
    sudo chown -R 1000:1000 ./open-webui-data
    

    Rocky 계열에서는 이 부분을 빼먹으면 원인 찾는 데 시간이 꽤 걸립니다. 방화벽은 접속 문제, SELinux는 파일 접근 문제로 나눠서 보면 훨씬 빨리 좁힐 수 있습니다.

    접속 테스트와 첫 모델 확인

    브라우저에서 아래 주소로 접속합니다.

    text

    http://서버IP:3000
    

    처음 접속하면 계정을 만들고, 관리자 화면에서 Ollama 연결 상태를 확인합니다. compose에서 OLLAMA_BASE_URL을 제대로 넣었다면 별도 설정 없이 모델 목록이 잡히는 편입니다.

    웹 UI 접속 확인 화면 분위기

    모델이 보이지 않으면 먼저 Ollama 컨테이너 안에서 pull을 실행합니다.

    bash

    docker exec -it ollama ollama pull llama3.2
    docker exec -it ollama ollama list
    

    Open WebUI 로그에서 연결 실패가 보이면 URL 설정을 다시 확인합니다.

    bash

    docker logs open-webui --tail=100
    

    자주 만나는 메시지는 대략 이런 형태입니다.

    bash

    httpx.ConnectError: [Errno 111] Connection refused
    

    이 경우 대부분 Open WebUI가 Ollama를 localhost로 찾고 있거나, 두 컨테이너가 같은 Docker 네트워크에 있지 않은 상황이었습니다.

    사양별 체감 속도와 선택 기준

    로컬 LLM은 설치보다 사양 선택에서 기대치를 맞추는 일이 더 중요합니다. 특히 CPU만 있는 서버와 GPU가 있는 서버는 체감 차이가 큽니다.

    제품명 가격대 주요 특징 추천 대상
    Oracle Cloud ARM Free Tier 무료 가능 비용 부담은 거의 없지만 LLM 추론은 느린 편 테스트용, 가벼운 모델 실험
    Intel N100 미니 PC 10만~20만원대 전력 소모가 낮고 24시간 운영 부담이 적음 홈서버 입문, 문서 요약 위주
    Ryzen 5급 미니 PC 30만~50만원대 CPU 추론 기준으로 N100보다 여유 있음 개인용 AI 서버, 여러 서비스 병행
    RTX 3060 12GB 데스크톱 중고가 변동 VRAM 12GB로 7B급 모델 운용이 수월함 응답 속도 중시, 로컬 AI 주사용
    RTX 4060 Ti 16GB 데스크톱 가격 변동 큼 VRAM 여유가 있어 모델 선택 폭이 넓음 장기적으로 로컬 LLM을 자주 쓸 사람

    제휴 링크를 넣는다면 표의 제품명이나 가격대 옆에 붙이는 방식이 자연스럽습니다. 다만 서버 부품은 가격 변동이 심하므로, 글을 발행할 때 실제 판매가와 재고를 한 번 더 확인하는 편이 좋습니다.

    홈서버 하드웨어 선택 분위기

    마무리 운영 포인트

    Rocky Linux 9에서 Ollama와 Open WebUI를 Docker로 묶으면 관리 자체는 단순합니다. 핵심은 Ollama 주소를 컨테이너 네트워크 기준으로 잡는 것, 그리고 firewalld와 SELinux를 Rocky 방식대로 처리하는 것입니다.

    처음부터 큰 모델을 올리기보다 llama3.2 같은 모델로 접속과 응답 흐름을 먼저 확인하고, 그다음 하드웨어에 맞춰 모델을 바꾸는 순서가 덜 피곤했습니다. 로컬 AI는 한 번에 완성하는 서비스라기보다, 홈서버에 하나 얹어두고 천천히 손에 맞게 조정하는 쪽에 가깝습니다.

  • 홈서버 자동화 작업 추천 기사 요약 텔레그램 알림 구성

    홈서버 자동화 작업 추천 기사 요약 텔레그램 알림 구성

    관심 기사 요약 자동화가 홈서버와 잘 맞는 이유

    관심 있는 기사만 골라 텔레그램으로 받아보는 자동화는 홈서버에서 돌리기 좋은 작업입니다. 매번 뉴스 앱을 열고, 키워드를 검색하고, 저장해두는 과정이 생각보다 번거롭기 때문입니다. RSS, 요약, 텔레그램 봇을 묶으면 매일 반복하는 확인 작업을 꽤 줄일 수 있습니다.

    자동화 작업 참고 이미지

    자동화 흐름은 단순할수록 오래 갑니다

    홈서버 자동화는 처음부터 크게 만들면 관리가 피곤해집니다. 직접 구성해보는 입장에서는 기사 수집 → 필터링 → 요약 → 텔레그램 전송 정도로 나누는 방식이 가장 무난합니다.

    예를 들어 관심 키워드를 미리 정해두고, RSS 피드에서 해당 키워드가 들어간 기사만 추려냅니다. 이후 원문 전체를 가져오기보다 제목, 발행처, 링크, 짧은 요약 정도만 정리해서 보내는 편이 깔끔합니다.

    특히 저작권이 있는 기사 본문을 통째로 저장하거나 재전송하는 방식은 피해야 합니다. 개인용 자동화라도 원문 링크 중심으로 확인하는 구조를 잡아두면 나중에 범위가 커졌을 때도 부담이 덜합니다.

    홈서버에 올리기 좋은 자동화 도구

    자동화 도구는 성향에 따라 선택이 갈립니다. 코드로 직접 짜는 방식이 편한 분도 있고, 화면에서 흐름을 연결하는 방식이 관리하기 쉬운 분도 있습니다.

    개인 홈서버 기준으로는 아래 세 가지가 후보에 오릅니다.

    제품 장점 단점
    n8n 시각적으로 흐름을 확인하기 좋아 RSS, HTTP 요청, 텔레그램 연동을 빠르게 구성할 수 있습니다 워크플로가 많아지면 백업과 버전 관리에 신경 써야 합니다
    Node-RED 가볍고 홈서버 자동화에 익숙한 사용자가 많아 센서·알림 연동까지 확장하기 좋습니다 뉴스 요약처럼 텍스트 처리 로직이 많아지면 흐름이 복잡해질 수 있습니다
    Python 스크립트 원하는 방식으로 세밀하게 제어할 수 있고 Docker 크론 작업으로 돌리기 좋습니다 예외 처리, 로그, 재시도 로직을 직접 챙겨야 합니다

    처음 구성한다면 n8n으로 흐름을 잡고, 나중에 로직이 복잡해지는 부분만 Python 스크립트로 분리하는 방식이 현실적입니다. 처음부터 완성형으로 만들기보다, 며칠 받아보면서 필터를 조정하는 쪽이 실제 사용에는 더 잘 맞습니다.

    홈서버 자동화 참고 이미지

    텔레그램 봇은 알림 채널로 쓰기 편합니다

    텔레그램은 봇을 만들고 채팅 ID만 확인하면 메시지를 보낼 수 있어 자동화 입문용으로도 부담이 적습니다. 홈서버에서 외부로 알림을 보내야 할 때 이메일보다 즉시성이 좋고, 메시지 형식도 비교적 다루기 쉽습니다.

    구성은 단순하게 잡을 수 있습니다.

    text

    RSS 피드 확인
    → 관심 키워드 필터링
    → 제목과 링크 추출
    → 짧은 요약 생성
    → 텔레그램 봇으로 전송
    

    여기서 중요한 건 요약 범위입니다. 기사 본문을 길게 복사해 보내기보다, 직접 읽을지 판단할 수 있을 정도의 짧은 메모만 남기는 편이 좋습니다. 예를 들면 “AI 반도체 수요 관련 기사, 기업 실적 전망 중심”처럼 요약은 짧게, 원문은 링크로 두는 방식입니다.

    저작권은 자동화할수록 더 조심해야 합니다

    뉴스 자동화에서 가장 애매해지는 부분이 저작권입니다. 사람이 직접 읽고 메모하는 것과 달리, 시스템이 정기적으로 본문을 수집하고 저장하면 성격이 달라질 수 있습니다.

    그래서 기준을 보수적으로 잡는 편이 좋습니다.

    항목 권장 방식
    기사 본문 전체 저장하지 않기
    텔레그램 전송 내용 제목, 출처, 링크, 짧은 요약 위주
    보관 기간 필요한 경우에만 짧게 유지
    공유 범위 개인 채팅방 또는 비공개 채널 중심
    원문 확인 공식 기사 링크로 이동

    이렇게 해두면 자동화의 편의는 가져가면서도, 불필요하게 원문을 복제하는 구조는 피할 수 있습니다. 여러 사람이 보는 채널에 보내는 경우라면 더 보수적으로 운영하는 것이 맞습니다.

    뉴스 요약 자동화 참고 이미지

    로그와 실패 처리는 꼭 챙겨야 합니다

    처음에는 메시지만 잘 오면 끝난 것처럼 보입니다. 하지만 며칠 돌려보면 RSS 피드가 일시적으로 막히거나, 특정 언론사 링크 형식이 바뀌거나, 요약 API 응답이 늦어지는 일이 생깁니다.

    그래서 최소한 실패 로그, 중복 전송 방지, 재시도 횟수는 넣어두는 것이 좋습니다. 같은 기사가 하루에 여러 번 오면 금방 피로해지고, 실패했는데 조용히 넘어가면 자동화의 신뢰도가 떨어집니다.

    Docker Compose로 올린다면 컨테이너 로그를 확인하기 쉽게 두고, 중요한 오류만 별도 텔레그램 알림으로 보내는 방식도 괜찮습니다. 홈서버 자동화는 화려한 기능보다 이런 기본기가 더 오래 갑니다.

    관심 키워드는 좁게 시작하는 편이 좋습니다

    처음부터 “IT 뉴스 전체”처럼 넓게 잡으면 알림이 너무 많아집니다. 차라리 홈서버, NAS, Docker, 보안 업데이트, AI 인프라처럼 직접 챙겨볼 주제를 좁게 정하는 편이 실사용에 맞습니다.

    며칠 받아본 뒤 필요 없는 키워드는 빼고, 자주 놓치는 주제는 추가하면 됩니다. 자동화는 한 번에 완성하는 작업이라기보다 알림의 밀도를 계속 맞춰가는 과정에 가깝습니다.

    텔레그램 알림 자동화 참고 이미지

    정리하면 정보 확인이 가벼워집니다

    기사 요약 자동화는 거창한 시스템이라기보다, 매일 반복하던 확인 작업을 줄이는 도구에 가깝습니다. 홈서버에 n8n이나 간단한 Python 스크립트를 올려두고 원문 링크 중심의 짧은 요약만 텔레그램으로 받아도 충분히 실용적입니다.

    다만 편하다는 이유로 기사 본문을 그대로 모으거나 공유하는 구조로 만들면 문제가 될 수 있습니다. 처음부터 “기사를 대신 읽어주는 도구”가 아니라 읽을 기사를 고르는 도구로 기준을 잡아두는 편이 좋습니다.

  • 홈서버 자동화 작업 추천 백업 알림 모니터링 구성

    홈서버 자동화 작업 추천 백업 알림 모니터링 구성

    자동화는 작은 반복 작업부터 시작합니다

    홈서버를 켜두고 나면 처음에는 파일 저장이나 개인 웹서비스 정도만 떠올리기 쉽습니다. 그런데 운영 시간이 쌓이면 매일 확인해야 하는 작업이 생각보다 많아집니다. 이때 자동화를 잘 잡아두면 서버를 들여다보는 시간이 줄고, 운영 부담도 꽤 가벼워집니다.

    홈서버 자동화 작업 구상

    처음부터 거창한 시스템을 만들 필요는 없습니다. 직접 구성해보면 백업, 알림, 정리, 모니터링처럼 반복 주기가 분명한 작업부터 자동화하는 쪽이 체감이 큽니다.

    예를 들어 매일 새벽 특정 폴더를 백업하고, 실패하면 텔레그램이나 디스코드로 알림을 받는 정도만 해도 충분히 실용적입니다. Oracle Cloud Free Tier 같은 ARM 서버, Synology NAS, Docker Compose 기반 홈랩에서도 이런 수준의 자동화는 비교적 부담 없이 시작할 수 있습니다.

    먼저 해볼 만한 자동화 작업

    자동화 후보는 많지만, 처음에는 실패해도 복구가 쉽고 성공하면 바로 편해지는 작업을 고르는 편이 좋습니다. 홈서버에서 자주 구성하는 흐름을 기준으로 정리하면 다음과 같습니다.

    작업명 주요 역할 추천 도구 추천 대상
    파일 백업 자동화 지정 폴더를 NAS, MinIO, 외장 스토리지로 주기 백업 rclone, restic, borg 사진·문서 보관이 많은 경우
    컨테이너 업데이트 확인 Docker 이미지 새 버전 확인 및 알림 Watchtower, Diun 여러 서비스를 Docker로 돌리는 경우
    서버 상태 알림 CPU, RAM, 디스크, 네트워크 상태 감시 Uptime Kuma, Netdata, Grafana 장애를 빨리 알고 싶은 경우
    로그 정리 오래된 로그 압축·삭제 logrotate, cron 디스크 용량이 자주 줄어드는 경우
    SSL 인증서 갱신 HTTPS 인증서 자동 갱신 Caddy, Traefik, Nginx Proxy Manager 외부 접속 서비스를 운영하는 경우
    미디어 파일 정리 다운로드 파일명 정리, 폴더 이동 FileBot, Sonarr, Radarr 개인 미디어 서버를 쓰는 경우
    사진 자동 분류 업로드 사진을 날짜별로 정리 Immich, PhotoPrism, Synology Photos 스마트폰 사진 백업이 많은 경우
    정기 리포트 발송 서버 상태나 백업 결과를 메일로 전달 cron, shell script, Gotify 점검 기록을 남기고 싶은 경우

    이 중 하나만 먼저 고른다면 백업 자동화가 가장 무난합니다. 홈서버는 결국 데이터를 오래 보관하는 장비에 가깝기 때문에, 서비스 편의 기능보다 백업 체계가 먼저 안정돼야 합니다.

    서버 자동화와 작업 흐름

    백업은 성공 여부 확인까지 포함해야 합니다

    백업 스크립트를 만들어두는 것만으로는 부족합니다. 중요한 건 백업이 실제로 성공했는지 확인하는 과정까지 자동화하는 것입니다.

    예를 들어 rclone으로 MinIO나 NAS에 백업을 보낸다면, 단순 복사 명령만 cron에 넣기보다 실행 결과를 로그로 남기고 실패 시 알림을 보내는 방식이 좋습니다. restic이나 borg를 쓰면 스냅샷 기반으로 보관할 수 있어, 실수로 지운 파일을 되돌리기에도 편합니다.

    기본 흐름은 아래처럼 잡을 수 있습니다.

    bash

    0 3 * * * /usr/local/bin/backup-home.sh >> /var/log/home-backup.log 2>&1
    

    이 설정은 매일 새벽 3시에 백업 스크립트를 실행하고 로그를 남기는 형태입니다. 여기에 스크립트 내부에서 실패 여부를 확인하고 알림까지 보내면 운영 부담이 확 줄어듭니다. 다만 실제 경로, 인증 정보, 저장소 주소는 환경마다 다르므로 그대로 복사하기보다 본인 서버 구조에 맞춰 조정해야 합니다.

    컨테이너 업데이트는 자동 적용보다 알림이 먼저입니다

    Docker Compose로 서비스를 여러 개 올려두면 업데이트 관리도 일이 됩니다. 그렇다고 모든 이미지를 자동으로 최신 버전으로 올리는 방식은 조심해야 합니다. 특히 데이터베이스, 인증, 리버스 프록시가 얽힌 서비스는 버전 변경만으로 설정이 깨질 수 있습니다.

    그래서 처음에는 자동 업데이트보다 업데이트 알림 자동화를 먼저 두는 편이 낫습니다. Diun 같은 도구로 새 이미지가 나왔는지만 알려받고, 실제 반영은 주말이나 점검 가능한 시간에 직접 하는 방식입니다.

    Watchtower도 편하지만, 개인 서비스가 여러 개 묶여 있다면 처음에는 모니터링 용도로만 써보는 것이 부담이 적습니다. 자동화의 목적은 손을 덜 대는 것이지, 문제가 생겼을 때 원인을 더 찾기 어렵게 만드는 것이 아닙니다.

    홈서버 모니터링과 자동화 구성

    알림 채널은 한곳으로 모으는 편이 좋습니다

    홈서버 자동화가 늘어나면 알림도 함께 늘어납니다. 백업 성공, 백업 실패, 디스크 용량 부족, 서비스 다운, 인증서 갱신 결과가 각각 다른 곳으로 오면 알림을 확인하는 일 자체가 피곤해집니다.

    이럴 때는 Gotify, Telegram Bot, Discord Webhook 중 하나를 정해 서버 알림을 한곳으로 모아두는 편이 관리하기 쉽습니다. 폐쇄적으로 쓰려면 Gotify가 깔끔하고, 외부에서도 바로 확인하려면 텔레그램이나 디스코드가 접근성이 좋습니다.

    알림 기준도 너무 촘촘하게 잡을 필요는 없습니다. 성공 알림은 하루 1회 요약으로 묶고, 실패나 용량 부족처럼 바로 확인해야 하는 항목만 즉시 알림으로 보내는 정도가 적당합니다.

    우선순위는 데이터 보호부터 잡습니다

    처음 홈서버 자동화를 구성한다면 순서를 정해두는 것이 좋습니다. 제 기준에서는 데이터 보호 → 장애 확인 → 유지보수 절감 → 편의 기능 순서가 안정적입니다.

    1. 백업 자동화와 복구 테스트
    2. 디스크 용량, 서비스 다운 알림
    3. 로그 정리와 인증서 갱신
    4. 컨테이너 업데이트 확인
    5. 사진, 미디어, 다운로드 파일 정리
    6. 정기 리포트와 대시보드 구성

    이 순서로 진행하면 서버 운영이 조금씩 손에 익습니다. 특히 복구 테스트는 한 번쯤 꼭 해보는 게 좋습니다. 백업 파일이 있어도 실제로 되돌려본 적이 없다면, 장애 상황에서 그 백업을 믿기 어렵습니다.

    정리해두면 운영이 가벼워집니다

    홈서버 자동화는 화려한 기능보다 반복 작업을 조용히 줄여주는 구성이 오래 갑니다. 백업, 알림, 모니터링, 정리 작업만 차근차근 묶어도 매번 서버에 접속해 확인하던 시간이 줄어듭니다.

    처음부터 모든 것을 자동화하려고 하기보다, 지금 가장 자주 확인하는 작업 하나를 골라 cron이나 Docker Compose 기반으로 작게 붙여보면 됩니다. 그렇게 하나씩 쌓아두면 홈서버는 단순한 장비를 넘어, 생활 속에서 꾸준히 일해주는 개인 인프라에 가까워집니다.

  • 로컬 LLM 모델 비교 Qwen Gemma 홈서버 선택 기준

    로컬 LLM 모델 비교 Qwen Gemma 홈서버 선택 기준

    로컬 LLM에서 먼저 보는 기준

    로컬 LLM을 홈서버에 올려보려면 모델 이름보다 먼저 확인할 것이 있습니다. 내 장비에서 계속 켜둘 수 있는지, 그리고 한국어 질문에 대한 답변 품질이 크게 무너지지 않는지입니다.

    로컬 LLM 홈서버 구성 참고 이미지

    Qwen과 Gemma는 로컬 실행 후보로 자주 언급되는 모델입니다. 다만 성격은 조금 다릅니다. Qwen은 코딩, 추론, 다국어 대응 쪽으로 폭넓게 확장하는 느낌이 강하고, Gemma는 구글 생태계 안에서 문서와 배포 흐름이 비교적 정돈된 편입니다.

    홈서버 기준에서는 최고 성능보다 GGUF, Ollama, LM Studio, llama.cpp 같은 실행 환경에서 얼마나 쉽게 굴러가는지가 더 중요할 때가 많습니다. GPU 여유가 크지 않다면 4bit 양자화 모델부터 확인하는 쪽이 현실적입니다.

    Qwen 쪽 인상

    Qwen은 공식 블로그 기준으로 Qwen3에서 0.6B, 1.7B, 4B, 8B, 14B, 32B 같은 dense 모델과 MoE 모델을 공개했습니다. 공식 안내에서도 Ollama, LM Studio, MLX, llama.cpp 등을 로컬 사용 도구로 언급하고 있어 홈랩 환경과 잘 맞는 편입니다.

    참고: Qwen3 공식 블로그

    한국어 사용감은 모델 크기와 양자화 상태에 따라 차이가 납니다. 작은 모델은 빠르지만 긴 문맥을 이어가는 글쓰기나 복잡한 질문에서는 답변이 짧아지는 편입니다. 개인 서버에서도 어느 정도 활용성을 기대하려면 8B 이상부터 체감이 좋아지는 편입니다.

    로컬 서버와 모델 비교 참고 이미지

    Gemma 쪽 인상

    Gemma는 Google AI 문서 기준으로 Gemma 4가 E2B, E4B, 12B, 26B A4B, 31B 구성을 갖고 있습니다. 문서상으로는 텍스트뿐 아니라 이미지, 비디오, 일부 모델의 오디오 입력까지 확장되어 있고, 로컬 실행용 GGUF와 QAT 체크포인트도 안내되어 있습니다.

    참고: Gemma 공식 문서

    Gemma의 장점은 문서 흐름이 깔끔하다는 점입니다. 어떤 크기를 고르면 되는지, 양자화 모델을 어디서 받아야 하는지 찾기 편합니다. 다만 이전 Gemma 계열은 라이선스나 사용 조건 확인이 필요했기 때문에, 실제 배포 전에는 현재 모델 카드와 라이선스 문서를 다시 보는 것이 좋습니다.

    Qwen과 Gemma 비교

    모델 가격 주요 특징 추천 대상
    Qwen 무료 공개 가중치 모델 중심 코딩, 추론, 다국어, 로컬 실행 도구 지원 폭이 넓음 홈서버에서 챗봇, 코딩 보조, RAG 실험을 함께 해보고 싶은 사람
    Gemma 무료 공개 가중치 모델 중심 공식 문서, QAT, GGUF, 구글 생태계 연동이 잘 정리됨 안정적인 문서와 배포 경로를 따라 차근차근 구성하려는 사람

    홈서버에서는 어떤 쪽이 편할까

    직접 홈서버에 올린다고 생각하면, 처음부터 큰 모델을 고르기보다 작은 모델부터 확인하는 쪽이 덜 피곤합니다. CPU나 저전력 서버라면 Qwen 4B급, Gemma E2B/E4B급 양자화 모델부터 테스트하고, GPU 여유가 있으면 8B 이상으로 넘어가는 방식이 현실적입니다.

    Oracle Cloud Free Tier ARM 같은 환경에서는 대형 모델 자체보다 API 서버, 벡터DB, 웹 UI를 함께 올릴 수 있는 균형이 더 중요합니다. Synology나 미니 PC도 마찬가지입니다. 모델 하나가 잘 돌아가더라도 메모리 여유가 부족하면 RAG나 웹 UI에서 금방 답답해질 수 있습니다.

    홈서버 로컬 AI 구성 참고 이미지

    정리

    코딩 보조와 실험 폭을 넓게 가져가고 싶다면 Qwen, 문서와 배포 흐름을 따라 안정적으로 시작하고 싶다면 Gemma가 편합니다.

    둘 다 로컬 LLM 입문용으로 충분히 의미가 있습니다. 다만 홈서버에서는 모델 이름만 보고 고르기보다 메모리, 양자화 포맷, 실행 도구 호환성을 먼저 맞춰보는 것이 시행착오를 줄이는 데 도움이 됩니다.

  • 리눅스 데스크톱 홈서버 입문자를 위한 GUI 관리 방법

    리눅스 데스크톱 홈서버 입문자를 위한 GUI 관리 방법

    터미널이 부담스러운 사람에게 맞는 출발점

    리눅스를 처음 데스크톱으로 쓰려는 분들이 가장 많이 부딪히는 지점은 성능보다 관리 방식입니다. 서버처럼 검은 터미널만 붙잡고 운영해야 한다고 생각하면 시작 전부터 부담이 커집니다. 하지만 요즘 리눅스 데스크톱은 생각보다 훨씬 친절합니다.

    리눅스 데스크톱 참고 이미지

    홈서버를 직접 굴리다 보면 SSH 접속, 패키지 업데이트, 로그 확인 같은 작업을 피하기 어렵습니다. 다만 처음부터 모든 작업을 명령어로 처리할 필요는 없습니다. 리눅스 데스크톱 환경을 올려두면 파일 관리, 네트워크 설정, 디스크 확인 같은 기본 작업을 화면에서 직접 확인하며 처리할 수 있습니다.

    GNOME, KDE Plasma, Xfce 같은 데스크톱 환경은 윈도우를 써본 사람이라면 비교적 빠르게 익숙해질 수 있는 구조입니다. 설정 앱에서 와이파이, 사용자 계정, 디스플레이, 업데이트를 확인할 수 있고, 파일 탐색기에서 공유 폴더나 외장 디스크에 접근하기도 쉽습니다.

    서버를 공부하려고 리눅스를 설치했는데 터미널 때문에 막히는 경우가 많습니다. 이럴 때 데스크톱 환경은 좋은 완충 장치가 됩니다. 명령어를 몰라도 시스템 상태를 눈으로 확인할 수 있다는 점이 초반 진입장벽을 낮춰줍니다.

    홈서버 입문자에게 맞는 선택

    홈서버 구축 관점에서 보면 리눅스 데스크톱은 “서버답지 않은 선택”이라기보다 관리 방식을 넓혀주는 선택에 가깝습니다. 예를 들어 Rocky Linux 9.x나 Ubuntu LTS 계열에 데스크톱 환경을 얹고, Docker Compose v2로 서비스를 관리하면 GUI와 터미널을 함께 사용할 수 있습니다.

    리눅스 데스크톱 작업 환경 참고 이미지

    NAS나 미니PC를 24시간 켜두는 환경이라면 화면을 항상 연결해둘 필요는 없습니다. 처음 설정할 때만 데스크톱으로 잡아두고, 어느 정도 익숙해진 뒤에는 SSH 접속 위주로 넘어가도 됩니다. 핵심은 처음부터 CLI만 고집하지 않아도 된다는 점입니다.

    제품 장점 단점
    Ubuntu Desktop LTS 자료가 많고 드라이버 지원이 좋아 입문자에게 부담이 적음 서버 용도로는 기본 설치 구성이 다소 무겁게 느껴질 수 있음
    Linux Mint 윈도우와 비슷한 사용감이라 적응이 빠름 서버 운영 자료는 Ubuntu Server 기준보다 상대적으로 적음
    Fedora Workstation 최신 커널과 패키지를 빠르게 경험할 수 있음 장기 운영보다는 업데이트 흐름을 꾸준히 따라가야 함
    Rocky Linux 9.x + GUI RHEL 계열 서버 환경과 가까워 운영 연습에 좋음 데스크톱 편의성은 Ubuntu 계열보다 덜 친절하게 느껴질 수 있음

    그래픽 환경이 항상 낭비는 아닙니다

    서버 운영을 조금 해본 분들은 데스크톱 환경을 올리면 리소스를 낭비한다고 말하기도 합니다. 틀린 말은 아닙니다. RAM이 1GB나 2GB 수준인 작은 VPS라면 GUI는 부담이 됩니다.

    하지만 집에서 쓰는 미니PC, 남는 노트북, Synology 외 별도 테스트 장비처럼 자원이 어느 정도 있는 환경이라면 이야기가 달라집니다. 배우는 속도와 관리 편의성도 운영 비용의 일부로 봐야 합니다.

    디스크 마운트가 꼬였을 때, 네트워크 연결을 확인할 때, Docker 볼륨 위치를 정리할 때 화면이 있으면 실수를 줄이기 쉽습니다. 터미널에서 lsblk, ip addr, journalctl을 하나씩 치는 방식도 중요하지만, 처음에는 GUI로 구조를 이해하고 필요한 순간에 명령어를 붙이는 흐름이 더 자연스럽습니다.

    리눅스 시스템 관리 참고 이미지

    어디까지 데스크톱으로 관리할까

    리눅스 데스크톱을 쓴다고 해서 모든 관리를 마우스로 끝낼 수 있는 건 아닙니다. Docker 컨테이너 재시작, 방화벽 설정, 서비스 로그 확인처럼 결국 터미널이 필요한 순간은 옵니다. 다만 그 시점이 꼭 설치 첫날일 필요는 없습니다.

    처음에는 업데이트, 파일 복사, 원격 접속 설정, 브라우저에서 관리자 페이지 접속 정도를 데스크톱으로 처리해도 충분합니다. 이후 Portainer, CasaOS, Cockpit 같은 웹 기반 관리 도구를 붙이면 터미널 의존도는 더 줄어듭니다. 터미널을 완전히 피하기보다 천천히 익숙해지는 구조가 현실적입니다.

    리눅스 데스크톱은 홈서버를 처음 만지는 사람에게 좋은 출발점이 될 수 있습니다. 서버 운영의 본질을 흐리는 선택이라기보다, 시스템을 눈으로 이해하고 실수를 줄이기 위한 발판에 가깝습니다. 어느 정도 익숙해진 뒤에는 필요한 서비스만 남기고 가볍게 운영하는 방향으로 바꿔도 늦지 않습니다.