HTTPS는 보안의 기본이고, 우리가 사용하는 일반적인 웹 서비스들은 HTTPS가 적용된다. HTTPS는 Certificate를 기반으로 암호화 해서 통신을 수행한다. 우리가 DDNS와 같은 도메인명을 얻고 Supabase에 Google OAuth를 연계하려면 필수 설정이다.

1 NPM 설정

Google OAuth는 HTTPS를 요구하므로 duckdns 주소에 SSL을 입혀야 한다. 별도 폴더에서 Nginx Proxy Manger (NPM)을 실행하여 HTTPS 관련 80, 443 포트를 점유하는 작업이다.

2.1. Nginx Proxy Manager (NPM) 실행

먼저 NPM을 실행하기 위한 docker-compose.yml 파일을 작성한다. (Supabase 폴더와는 별도의 폴더, 예: ~/npm에서 진행하자.)

# ~/npm/docker-compose.yml
version: '3.8'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'    # HTTP 트래픽
      - '443:443'  # HTTPS 트래픽
      - '81:81'    # 관리자 UI 포트
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
  • 실행: dockercompose up -d
  • 관리자 접속: http://[Mini_PC_IP]:81
  • 초기 계정: 자기 내용으로 설정

2.2. 사전 준비: 공유기 포트포워딩

외부에서 your-home.duckdns.org로 들어오는 신호가 미니 PC로 전달되어야 SSL 발급과 접속이 가능합니다. ipTime 공유기 설정에서 다음 포트를 미니 PC 내부 IP로 연결하자자.

  • 80 -> 80 (Let's Encrypt 인증용)
    • 규칙이름: HTTP_Auth (자유롭게 입력 가능)
    • 내부 IP주소: 미니 PC의 내부 IP 주소를 입력 (예: 192.168.0.XX)
    • 프로토콜: TCP
    • 외부 포트: 80 ~ 80
    • 내부 포트: 80
  • 443 -> 443 (HTTPS 접속용)
    • 규칙이름: HTTPS
    • 내부 IP주소: 미니 PC의 내부 IP 주소 (위와 동일)
    • 프로토콜: TCP
    • 외부 포트: 443 ~ 443
    • 내부 포트: 443

2.3. NPM에서 라우팅 및 SSL 설정

NPM 관리자 UI(:81)에 접속하여 다음 순서로 설정을 진행합니다.

1단계: Proxy Host 추가

  1. Dashboard > Proxy Hosts > Add Proxy Host 클릭.
  2. Details 탭:
    • Domain Names: your-home.duckdns.org` 입력 후 엔터.
    • Scheme: http 선택.
    • Forward Hostname / IP: 미니 PC의 내부 IP (예: 192.168.0.10) 입력.
    • Forward Port: 8000 입력.
    • Block Common Exploits: 활성화 (보안 권장).
    • Websockets Support: 활성화 (Supabase 실시간 기능을 위해 필수)

2단계: SSL 발급 (Let's Encrypt)

  1. SSL 탭으로 이동합니다.
  2. SSL Certificate: Request a new SSL Certificate 선택.
  3. Force SSL: 활성화 (HTTP 접속 시 자동으로 HTTPS 전환).
  4. HTTP/2 Support: 활성화.
  5. I Agree to the Let's Encrypt Terms of Service: 체크. (이 부분은 보이지 않음)
  6. Save 클릭. (인증서 발급까지 약 30초~1분 정도 소요된다.)

이렇게 등록한 Let's Encrypt의 인증서는 NPM이 자동으로 업데이트 한다고 한다.

마무리 하며

앞에서 언급한 것과 같이 Supabase를 Mini PC에 등록한 후에, 이 Supabase를 통해 Google OAuth 연계하려면 필수 설정이다. 인증서는 NPM이 자동으로 갱신하지만, 3개월마다 한 번씩 NPM 대시보드에서 만료일이 잘 연장되고 있는지 확인해보는 습관이 필요하다.

ipTime으로 DDNS를 설정하는 것이 가장 간단하지만, HTTP를 지원하기 위해서는 도메인의 소유권 확인이 가능해야 한다. 이를 가능케 하면서 DDNS가 가능한 서비스가 duckdns였다. 도메인을 등록하고 우리 Wireless AP의 Public 주소를 등록해 두면 된다. 여기서는 이 Public

1 DNS 설정

  1. https://www.duckdns.org/ 에 접속하고 가입한다. Google 계정으로 쉽게 가능하다.
  2. Subdomain 입력하는 창에 원하는 이름을 입력하고 "add domain"을 클릭한다.
  3. 최대 5개까지 가능하다.

이 간단한 동작으로 우리의 domain이 생기게 된다.

2. IP 주소 업데이트 자동화

참고로 Duck DNS에서 주소를 얻고, 집에서 IP를 지속 업데이트하기 위해서 Cron을 추가 설정하는 방법이 있다. 공인 IP는 언제든 바뀔 수 있습니다. 서버에서 5분마다 자동으로 IP를 체크해서 DuckDNS에 알려주도록 설정하는 것이 가장 좋다.

  1. 터미널에서 아래 명령어로 스크립트 파일을 만듭니다.
  2. mkdir -p ~/duckdns nano ~/duckdns/duck.sh
  3. 아래 내용을 복사해서 붙여넣으세요 (여기서 domain은 your-home으로 가정하고, TOKEN은 DuckDNS 홈페이지 메인에 있는 본인의 토큰을 복사해 넣자.).
  4. echo url="https://www.duckdns.org/update?domains=your-home&token=YOUR_TOKEN&ip=" | curl -k -o ~/duckdns/duck.log -K -
  5. 파일을 저장(Ctrl+O, Enter)하고 닫는다(Ctrl+X).
  6. 실행 권한을 주고 테스트해본다.
chmod 700 ~/duckdns/duck.sh
~/duckdns/duck.sh
cat ~/duckdns/duck.log # OK라고 나오면 성공입니다.
  1. 주기적으로 실행되게 크론탭(crontab)에 등록합니다.
crontab -e
파일 맨 밑에 아래 한 줄을 추가하고 저장합니다. 
*/5 * * * * ~/duckdns/duck.sh >/dev/null 2>&1

마무리

간단한 동작으로 도메인이 생겼다. Supabase, n8n은 이 간단한 기능으로 활용이 가능하다.

네트워크 서비스를 만들다 보면 가장 먼저 마주하는 벽이 있습니다. 바로 "어떻게 내 서비스를 외부에서 접속하게 만들 것인가?"이죠. 단순히 IP 주소를 알려줄 순 없으니까요.

 

오늘은 입문자가 쉽게 시작할 수 있는 DDNS 방식부터, Vercel이나 Firebase 같은 전문 플랫폼 연동을 위한 Cloudflare 기반 고유 도메인 확보까지 2단계로 나누어 정리해 보겠습니다.

 

1단계 전략: 가볍게 시작하는 '빌려 쓰는 주소', DDNS

처음 개인 서버(Mini PC, NAS)를 구축하거나 로컬 환경에서 테스트할 때 가장 만만한 방법은 DDNS(Dynamic DNS)입니다.

  • 원리: iptime 공유기나 DuckDNS 같은 서비스가 제공하는 서브 도메인(예: myhome.duckdns.org)을 빌려 쓰는 방식입니다.
  • 장점: 무료입니다. 공유기의 유동 IP가 바뀌어도 자동으로 추적해 주니 신경 쓸 게 없습니다.
  • 한계: 하지만 치명적인 단점이 있습니다. '제어권'이 없다는 거죠.
    • example.com 같은 깔끔한 주소를 가질 수 없습니다.
    • 가장 큰 문제는 DNS 레코드(TXT, MX 등) 수정이 거의 불가능하다는 점입니다. 이 때문에 Resend 같은 외부 이메일 발송 서비스나 보안 인증이 필요한 플랫폼과는 연동할 수 없습니다.

💡 한 줄 요약: "내 방 안의 장난감이나 개인용 미디어 서버라면 DDNS로 충분합니다."

2단계 전략: 본격적인 내 집 마련, Cloudflare로 고유 도메인 확보하기

서비스를 Vercel, Firebase에 배포하고 프로젝트의 완성도를 높이고 싶다면, 이제는 나만의 고유 도메인이 필요합니다. 이때 가장 추천하는 조합이 바로 Cloudflare입니다.

왜 Cloudflare인가요?

  1. 도매가 정책: Cloudflare는 도메인 판매 시 수수료를 붙이지 않는 '원가 제공'으로 유명합니다. 갱신 비용이 매우 저렴하죠.
  2. 강력한 관리 도구: 단순 구매뿐만 아니라 전 세계에서 가장 강력한 DNS 관리 기능을 제공합니다.
  3. 무료 보안(SSL/WAF): 클릭 몇 번으로 HTTPS 보안 설정을 끝낼 수 있고, 디도스(DDoS) 공격으로부터 내 서버 IP를 숨겨줍니다.

Vercel & Firebase 연동의 핵심

고유 도메인이 있으면 비로소 전문적인 설정이 가능해집니다.

  • Vercel/Firebase 연결: 제공받은 CNAME 레코드를 Cloudflare DNS 설정에 입력하기만 하면 끝납니다.
  • 외부 서비스 연동: Resend 같은 서비스에서 요구하는 TXT 레코드를 직접 등록할 수 있어, 내 도메인 이름으로 이메일을 보내는 '진짜 서비스' 운영이 가능해집니다.

 

한눈에 비교하기

구분 1차: DDNS (DuckDNS 등) 2차: 고유 도메인 (Cloudflare 등)
비용 무료 연간 구독료 (약 1~2만 원대)
주소 형태 user.duckdns.org myproject.com
권한 단순 접속만 가능 모든 DNS 레코드(A, MX, TXT) 수정 가능
추천 용도 로컬 테스트, 개인 NAS 서비스 배포, 포트폴리오, 이메일 연동

마치며: 어떤 선택이 좋을까?

결국 '어디까지 제어하고 싶은가'의 문제입니다.

 

단순히 집 프로젝트를 밖에서 보고 싶다면 DDNS로 시작하세요. 하지만 Vercel이나 Firebase에 배포한 내 작품에 날개를 달아주고, Resend 같은 전문 API 서비스까지 붙여보고 싶다면 망설이지 말고 Cloudflare에서 도메인을 하나 마련하시길 추천합니다.

커피 두세 잔 정도의 연간 비용으로, 여러분 서비스의 신뢰도가 완전히 달라질 테니까요!

n8n은 https://n8n.io/ 에서 가입하면 일정 기간 무료로 사용할 수 있습니다. 하지만, 시간 제약이 있으니 고급 사용자들은 자신의 컴퓨팅 환경에 설치해서 사용합니다. 여기서는 Linux가 설치된 Mini PC에 설정하는 그 내용에 대해서 정리 합니다.

1. 사전 준비 (Prerequisites)

설치를 시작하기 전, Mini PC에 다음 요소들이 준비되어 있어야 합니다.

  • OS: Ubuntu 22.04 LTS 또는 데비안 계열 리눅스 권장 (Windows/macOS도 가능)
  • 사양: CPU 2코어, RAM 4GB 이상 권장
  • Docker 설치: 도커와 도커 컴포즈(Docker Compose)가 설치되어 있어야 합니다.

2. Docker Compose를 이용한 설치 단계

Docker Compose를 사용하면 n8n 설정뿐만 아니라, 데이터를 저장할 데이터베이스까지 한 번에 관리할 수 있습니다.

단계 1: 작업 디렉토리 생성

터미널을 열고 n8n 설정 파일을 저장할 폴더를 만듭니다.

mkdir n8n-local && cd n8n-local

단계 2: docker-compose.yml 파일 작성

텍스트 에디터(nano 또는 vi)를 사용하여 설정 파일을 만듭니다.

nano docker-compose.yml

아래 내용을 복사해서 붙여넣으세요:

version: "3"
services:
  n8n:
    container_name: n8n-server
    # ... 기존 설정 ...
    image: n8nio/n8n:latest
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=my-n8n.duckdns.org
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - N8N_EDITOR_BASE_URL=https://my-n8n.duckdns.org
      - WEBHOOK_URL=https://my-n8n.duckdns.org
      - N8N_PROXY_HOPS=1
      - NODE_ENV=production
      - GENERIC_TIMEZONE=Asia/Seoul
      - N8N_SECURE_COOKIE=false # (외부 https로만 쓸 거면 최종적으로 true 권장)
    volumes:
      - ~/.n8n:/home/node/.n8n

단계 3: 컨테이너 실행

설정을 마쳤다면 다음 명령어로 n8n을 실행합니다.

docker compose up -d

이 명령어는 백그라운드에서 n8n을 실행하며, 필요한 이미지를 자동으로 다운로드합니다.

3. n8n 접속 및 초기 설정

  1. Mini PC의 브라우저(또는 같은 네트워크의 다른 PC)에서 다음 주소로 접속합니다.
    • http://localhost:5678 (Mini PC 본체인 경우)
    • http://[Mini-PC-IP주소]:5678 (외부 기기인 경우)
  2. 처음 접속 시 관리자 계정(이메일 및 비밀번호)을 생성하는 화면이 나옵니다.
  3. 계정 생성 후 n8n 대시보드에 진입하면 설치가 완료됩니다!

4. 외부 접속을 위한 도메인 연결(SSL 설정)

외부 연결은 여기서는 상세히 다루지는 않습니다. 단지, 필요한 설정 내용만 언급합니다.

  • Wireless AP에서 https를 위한 Port Forwarding 설정
  • DDNS를 설정 (e.g. duckdns)
  • Nginx Proxy Manager에 Domain 이름, SSL 그리고 내부 서비스 연결 설정

마무리 하며

같은 도메인명에 Custom Location으로 설정하는데 반나절 정도를 사용했었다. 주요 이슈는 2가지였습니다.

 

하나는 custom location에 n8n 명을 추가하면 인식이 되지 않는 것이었습니다. docker-compose.yml에 이 내용을 추가하는 것으로 local 테스트를 했지만, 성공하지 못하고 동작하지 않는 것을 확인하는 수준이었습니다. rewrite하는 방법으로 n8n 명을 추가하는 것까지는 성공하였다. 하지만 다른 문제가 있었습니다.

 

두 번째는 Supabase의 KONG과 충돌 난다는 것이었습니다. local에서 동작하는 부분을 확인하고 ChatGPT와 curl 명령을 이용해서 접근하는 방법을 디버깅했습니다. 이 때, Supabase와 KONG과 충돌을 파악하고 도메인을 분리 접근하는 방법으로 해결하였습니다.

익숙함의 안락함, 그리고 새로운 도전 개발 초기부터 제 메인 환경은 늘 Windows였습니다. 모바일 빌드 환경이 윈도우 기반인 경우가 많았고, 그 익숙함에 안주해왔죠. 대학원 시절 Linux 과제를 수행하고 Solaris System Admin을 경험하며 Unix/Linux가 낯설지는 않았지만, 늘 '서브'에 머물렀기에 배움은 더뎠고 익숙해질 만하면 잊어버리곤 했습니다. Mac은 그저 동료의 화면을 잠시 보거나 테스트용 구형 인텔 맥을 만져본 것이 전부였죠.

AI 시대, Linux가 메인이 될 수 있다는 확신 최근 AI 기술을 깊이 파고들면서 깨달았습니다. 이제 특수한 윈도우 전용 작업을 제외한 대부분의 개발 워크플로우가 Linux에서 더 효율적으로 돌아간다는 사실을요. 새로 산 노트북의 리눅스 설정이 불안정해, 결국 기존에 쓰던 노트북에 과감히 Ubuntu를 설치하고 하루 만에 다음의 세팅을 마쳤습니다.

  • 시스템 최적화: Ubuntu 설치 및 nVidia 그래픽 드라이버 설정

  • AI 개발 환경: Claude Code, Codex, Gemini CLI (with Node.js, NVM)

  • 차세대 도구: Antigravity IDE, Beads, Gastown (with Go)

  • 생산성 워크플로우: OneDrive 연동, Obsidian, Discord 설치

도구가 바뀌니 두려움이 사라졌습니다 오는 월요일에는 Mac Mini M2를 새로 들여올 예정입니다. 아마 오늘 했던 설정들을 똑같이 반복하게 되겠지요. 예전 같았으면 이 환경을 구축하는 데만 며칠, 혹은 몇 달이 걸릴까 봐 지레 겁부터 먹었을 텐데, 이제는 전혀 두렵지 않습니다.

제 곁에는 복잡한 설정을 순식간에 가이드해 주는 강력한 AI 에이전트들이 있으니까요. OS라는 벽이 무너지고, 오직 '무엇을 만들 것인가'에만 집중할 수 있는 시대가 왔음을 실감합니다.

AI로 PoC를 많이 하다 보면, 여러 Cloud 기능을 쓰게 되는데 요즘은 Supabase와 Vecel을 많이 쓰게 되는 것 같다. Supabase는 개발자가 백엔드 인프라를 직접 구축하는 수고를 덜어주는 오픈소스 서비스형 백엔드(Backend as a Service, BaaS) 플랫폼이다. 'Firebase의 오픈소스 대안'으로 불리며, 다음과 같은 핵심 기능을 제공한다

  • Database (PostgreSQL): 모든 프로젝트에는 전용 PostgreSQL 데이터베이스가 제공되며, SQL을 직접 작성하거나 편리한 대시보드를 통해 데이터를 관리할 수 있습니다.
  • Authentication: 이메일, 비밀번호 기반 로그인뿐만 아니라 Google, GitHub 등을 이용한 소셜 로그인(OAuth) 기능을 쉽게 연동할 수 있습니다.
  • Storage: 이미지, 비디오, 문서와 같은 대용량 미디어 파일을 저장하고 관리할 수 있는 환경을 제공합니다.

이 기능을 Web에서 Free 플랜으로 사용하면 프로젝트를 2개까지 사용가능하다. 다행히, Supabase를 Mini PC에서 사용할 수 있어 여기서 설정해 보자.

구분 Supabase Cloud (무료) Mini PC Self-hosting
비용 용량/트래픽 초과 시 과금 0원 (전기세 제외)
데이터 용량 보통 500MB 제한 내 SSD 용량 전체
동시 접속자 제한 있음 하드웨어 사양만큼 무제한
데이터 주권 외부 서버에 저장 내 미니 PC에 저장

1. 기본 가정 및 공통 인프라 설정 (0단계)

본격적인 설치 전, 서버의 기초 뼈대를 잡는다. 이 설정은 한 번만 수행하면 된된다.

  • Linux OS: Ubuntu 24.04 등 안정적인 배포판 권장.
  • Docker 환경: docker-ce, docker-compose-plugin 설치 완료.
  • 보안 (VPN & UFW):
    • VPN: 내부망 접속을 위한 안전한 통로 확보 (ipTIME VPN 등).
    • UFW: 기본 방화벽 활성화 및 기본 포트 허용 (sudo ufw allow ssh, sudo ufw enable).
  • 주소 (DDNS): 외부 접속용 고정 주소 확보 (예: my-home.iptime.org).

2. 첫 번째 Supabase 프로젝트 구축 (Project A)

웹에서 '프로젝트 하나'를 만드는 것과 동일한 과정이이다.

1) 파일 준비 및 기본 설정

여기서 DDNS와 supabase의 port는 다르게 하면 된다.

# 프로젝트 A 전용 폴더 생성
mkdir -p ~/supabase/project-a && cd ~/supabase/project-a
git clone --depth 1 https://github.com/supabase/supabase .
cd docker
cp .env.example .env

2) .env 디테일 수정

  • URL 설정:
    • API_EXTERNAL_URL=http://my-home.iptime.org:8000
    • GOTRUE_EXTERNAL_URL=http://my-home.iptime.org:8000
  • 보안 키: JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY를 고유한 문자열로 변경.
  • DB 암호: POSTGRES_PASSWORD를 강력한 비밀번호로 변경.

experimental이기는 하지만, 다음 명령을 사용하면 바로 .env의 주요 값을 생성해서 업데이트 해주는 명령도 있다.

sh ./utils/generate-keys.sh

3) 서비스 기동 및 포트 개방

  • Docker 실행: docker compose up -d
  • UFW 허용: sudo ufw allow 8000/tcp
  • 포트 포워딩 (공유기): 외부 8000 -> 미니 PC 8000 (TCP) 설정 추가.

3. 두 번째 Supabase 프로젝트 추가 (Project B)

동일한 장비에서 완전히 분리된 별도의 프로젝트를 운영한다.

1) 인스턴스 격리 복제

# 프로젝트 B 전용 폴더 생성 (데이터와 설정의 완전한 분리)
mkdir -p ~/supabase/project-b && cd ~/supabase/project-b
git clone --depth 1 https://github.com/supabase/supabase .
cd docker
cp .env.example .env

2) 충돌 방지를 위한 디테일 수정

  • 포트 차별화: 중복을 피하기 위해 다른 포트(e.g. 8001) 를 사용한다.
    • .envKONG_HTTP_PORT=8001 수정.
    • 모든 외부 URL 끝을 :8001로 변경 (예: http://my-home.iptime.org:8001).
  • 프로젝트 이름 지정: docker-compose.yml 파일 최상단에 name: supabase-b 추가.
    • 이렇게 해야 컨테이너 이름(예: supabase-b-db)이 첫 번째 프로젝트와 겹치지 않는다.

3) 서비스 기동 및 포트 개방

  • Docker 실행: docker compose up -d
  • UFW 허용: sudo ufw allow 8001/tcp
  • 포트 포워딩 (공유기): 외부 8001 -> 미니 PC 8001 (TCP) 설정 추가.

🔒 4. 마무리 및 통합 관리

두 인스턴스가 독립적으로 운영되는 상태에서의 관리 포인트입니다.

  • 상태 모니터링:
    • docker compose -p supabase-a ps: 첫 번째 프로젝트 상태 확인.
    • docker compose -p supabase-b ps: 두 번째 프로젝트 상태 확인.
  • 로그 관리: 특정 프로젝트의 에러 확인 시 -p 옵션 사용 (docker compose -p supabase-b logs -f realtime).
  • 리소스 배분: 미니 PC의 사양에 따라 docker stats로 메모리 점유율을 확인하며 운영하자.

핵심 요약

  1. 하나의 인스턴스 = 하나의 프로젝트: 폴더별로 docker-compose를 실행하여 독립적인 환경을 구축한다.
  2. 포트 포워딩은 건별로: 인스턴스마다 사용하는 KONG_HTTP_PORT가 다르므로, 공유기 설정도 그에 맞춰 하나씩 추가해야 한다.
  3. 이름 격리의 중요성: yml 파일에 프로젝트 name을 명시해야 컨테이너 이름 충돌로 인한 실행 실패를 막을 수 있다.

이 가이드는 로컬 개발 환경을 넘어, 실제 여러 서비스를 한 대의 서버에서 운영할 때 가장 표준적인 방법이다.

2025년 12월 말, Mini PC의 활용에 대해 여러 사람들에게 들었다. 개발 작업에 Windows를 활용하고 있었지만, 오랫만에 Linux 시스템도 하나 설치하고 싶었다. 집에 Ubuntu 설치를 했고, 가장 처음 한 것은 서비스들을 돌려보고 싶어 가장 기본이 되는 Docker를 설치했다. 이 때, 여러 어려움이 있어, 경험을 정리해 본다. 어려움이라고 했지만, 돌아 보니 퀴즈 혹은 퍼즐을 풀어 보는 느낌이었다.

0. 기존 시도

일반적으로 인터넷 검색, LLM 문의를 통해서 접근하는 방법은 다음과 같다.

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

404 에러가 발생되고, docker의 심장이라고 할 수 있는 기능인 container 설치가 안된다. 반나절 정도 여러 시도 끝에 성공했다.

1. 현상: containerd.io 설치 불가 (404 Not Found)

Docker 공식 가이드를 따라 apt install을 시도했으나, 저장소 메타데이터에 등록된 containerd.io 최신 버전(2.x.x)이 실제 서버에 존재하지 않아 설치가 중단되는 현상이 발생했다.

  • 원인: Ubuntu 24.04(Noble) 저장소의 일시적인 인덱스 동기화 오류.
  • 해결: apt를 거치지 않고, 안정성이 검증된 .deb 패키지 파일을 직접 다운로드하여 수동 설치(dpkg)를 진행했다.
# 안정 버전 수동 다운로드 및 설치
wget https://download.docker.com/linux/ubuntu/dists/noble/pool/stable/amd64/containerd.io_1.7.25-1_amd64.deb
sudo dpkg -i containerd.io_1.7.25-1_amd64.deb

2. 의존성 해결 및 버전 고정 (apt-mark hold)

수동 설치한 containerd.io와 나머지 Docker 패키지 간의 버전 충돌(Dependency Hell)이 발생했다. apt가 자꾸 존재하지 않는 상위 버전으로 업데이트를 시도하며 설치를 거부하는 상황을 해결했다.

  • 해결: 설치 가능한 docker-ce 버전을 확인하고, 다운로드를 허용하여 버전을 강제 일치시킨 뒤 업데이트를 잠갔습니다.
# 1. 버전 강제 지정 및 다운로드 허용 설치
sudo apt install -y --allow-downgrades \
  docker-ce=5:27.5.1-1~ubuntu.24.04~noble \
  docker-ce-cli=5:27.5.1-1~ubuntu.24.04~noble

# 2. 향후 자동 업데이트로 인한 꼬임 방지 (버전 고정)
sudo apt-mark hold docker-ce docker-ce-cli containerd.io

3. containerd 서비스 유닛 수동 등록

수동으로 설치한 패키지는 시스템 서비스(systemd)에 자동으로 등록되지 않는 경우가 있다. 이로 인해 Unit containerd.service not found 에러가 발생하며 Docker 엔진의 심장이 뛰지 않는 상태가 되었다.

  • 해결: /lib/systemd/system/containerd.service 파일을 직접 생성하여 런타임 환경을 수동으로 구성했습니다.
# 서비스 파일 수동 생성 후 로드
sudo nano /lib/systemd/system/containerd.service  # 서비스 정의 내용 입력
sudo systemctl daemon-reload
sudo systemctl enable --now containerd

containerd.service의 내용은 다음과 같이 입력했다.

[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/bin/containerd
Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=1048576
TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target

4. 최종 Docker 실행 및 검증

심장(containerd)이 뛰기 시작하자 몸체(docker-ce)도 정상적으로 작동하기 시작했다.

  • 결과: 서비스 가동 및 권한 설정을 마치고, 드디어 Supabase를 올릴 준비가 끝났다.
# Docker 서비스 시작 및 확인
sudo systemctl start docker
sudo systemctl status docker  # Active: active (running) 확인!

# 작동 확인
docker ps

마무리 하며

예전에는 Googling, Stack overflow에서 이슈를 해결했다. LLM이 검색을 지원하기 시작하면서 여러 경로 해결 방안을 찾아 해설해 준다. 특히, Ubuntu의 기본적인 package 설정을 따라서 하게 되면, 서비스의 설정도 알아서 해준다. debian package를 직접 받아, 서비스의 환경 설정을 하고, 그 후에 dependency가 있는 기능들의 version을 매칭해서 동작을 확인하였다.

이런 예외 상황은 힘들지만, 해결할 수 있다는 것은 내가 시스템에 대해서 좀 더 이해하게 된다는 측면에서 흥미로운 퍼즐을 푸는 듯한 재미를 준다.

👋 개요

앞에서 GCP에 적용하기로 한 뉴스 요약 앱을 만들기 위한 여정을 두 단계로 나누어 정리한다.

  • 1단계는 Google Cloud 기반의 백엔드 구축과 자동화에 중점을 두었고,
  • 2단계는 Firebase 인증과 React Native Expo 앱을 개발하여 실제 사용자 경험을 다듬은 단계입니다.

✅ 1단계: Cloud 기반 백엔드 + 뉴스 요약 자동화

🎯 목표

  • Google News에서 원하는 키워드로 뉴스 수집
  • Gemini API를 활용한 요약 처리
  • 사용자별 요약 결과 저장
  • 자동 실행 및 확장 가능한 구조로 구축

🧱 주요 기술 스택

구성 요소 기술/서비스 설명
뉴스 수집 Python (requests, bs4) Google News 크롤링
요약 처리 Gemini Flash API 텍스트 요약 모델 호출
API 서버 FastAPI + Cloud Run RESTful API 제공
데이터 저장 Firestore (NoSQL) 사용자별 키워드 및 요약 결과 저장
인증 Firebase Authentication Google 로그인 기반 사용자 인증
예약 실행 Cloud Scheduler + Functions 키워드별 뉴스 수집/요약 자동 실행
배포 GitHub Actions + Cloud Build CI/CD 파이프라인

🔄 동작 흐름 요약

  1. 사용자가 관심 키워드를 등록
  2. Cloud Scheduler가 주기적으로 트리거 발행
  3. Cloud Function이 사용자별 Keyword를 취합하고 Pub/Sub을 통해서 FastAPI의 /summary API 호출
  4. 결과를 Firestore에 사용자별로 저장

📁 예시 Firestore 스키마

{
  "users": {
    "userId123": {
      "keywords": {
        "AI": {
          "2025-07-06": {
            "title": "OpenAI launches GPT-5",
            "summary": "OpenAI unveiled GPT-5 today with multimodal capabilities..."
          }
        }
      }
    }
  }
}

특이 사항

이 때, 특이 사항으로는 사용자 Id에서도 document를 만들지 않으면 접근이 되지 않는 부분이 있어서 debugging에 어려움이 있었다. 문제는 아래의 코드에서 를 통해서 keywords_ref = db.collection("users").document(uid).collection("keywords")를 통해서 Firebase에 접근하려고 할 때, 데이터가 읽히지 않는 것이었다. 이는 uid수준에 document가 없기 때문이었다.

def fetch_all_user_keywords():
    db = firestore.Client()
    users = db.collection("users").stream()
    users_list = list(users)

    result = []
    for user in users_list:
        uid = user.id
        keywords_ref = db.collection("users").document(uid).collection("keywords")
        keywords = [doc.to_dict()["keyword"] for doc in keywords_ref.stream()]
        if keywords:
            result.append({"user_id": uid, "keywords": keywords})

    print(f"result {result}")
    return result

여러가지 실험들이 있었고, 그 실험을 통해서 문제점에 대해서 이해하고 Database에 keyword를 추가할 때, 상위 user document가 없다면 빈 document를 만드는 코드를 추가하여, 문제가 해결되는 것을 확인하였다.

def add_keyword(user_id: str, keyword: str) -> str:
    # ✅ 상위 user 문서가 없다면 빈 문서라도 생성 (merge=True)
    db.collection("users").document(user_id).set({}, merge=True)

    keyword_ref = db.collection("users").document(user_id).collection("keywords")
    existing = keyword_ref.where("keyword", "==", keyword).limit(1).stream()
    if any(existing):
        raise ValueError("Keyword already exists")

    doc_ref = keyword_ref.document()
    doc_ref.set({
        "keyword": keyword,
        "created_at": datetime.utcnow().isoformat()
    })
    return doc_ref.id

✅ 2단계: React Native 앱으로 사용자 경험 구현

🎯 목표

  • 사용자 로그인
  • 키워드 등록 및 요약 리스트 보기
  • 앱 내에서 요약 결과 확인
  • 다크/라이트 모드 지원

🧱 주요 기술 스택

구성 요소 기술/서비스 설명
프레임워크 React Native (Expo) 크로스 플랫폼 앱 개발
인증 Firebase Authentication Google 로그인 연동
데이터 통신 REST API (FastAPI) 백엔드와 데이터 연동
앱 배포 EAS Build + Expo Go Android/iOS 앱 테스트 및 배포
디자인 React Native Paper 테마 기반 다크/라이트 모드 지원

📱 주요 UI 기능

  • 🔑 로그인 화면: Firebase 인증 기반
  • 📝 키워드 입력: 관심 주제 등록
  • 📄 요약 리스트: 날짜별 요약 보기
  • 🎨 다크/라이트 모드: 시스템 설정 연동
  • 🚫 백버튼 제어: 앱 종료 버튼으로 대체

💡 화면 예시 흐름

  1. 로그인 화면
  2. → 요약 리스트 화면
  3. → 키워드 등록 화면

특이 사항

Frontend 개발 시 특이 사항은 React Native Expo였다. Web 기반 React를 다룰 때도 그랬지만, 가이드가 최신 버전과 충돌이 있는 부분이 있었다. Web에서 Tailwindcss의 초기 설정이 달라져서 적절한 설정을 찾는데 어려움이 있었던 것과 같이 Expo의 SDK 최신이 53이었는데 Firebase 연동에 문제가 있다는 것을 알았다(1). 그래서, SDK 버전을 52로 낮추고 Expo Go 앱도 낮은 버전 찾아서 설치하면서 문제를 해결했다.

React Native Expo의 중간 테스트는 Expo Go라는 앱으로 로컬에서 실행하는 내용을 가져와서 테스트하기 때문에 로컬 서버가 항상 동작하고 있어야했다. 이것은 중간 테스트하는 결과물이고 최종 결과물은 Android 혹은 iOS에서 동작하는 결과물을 만들어야 하는 것이다. 이 결과물을 만드는 과정을 EAS(Expo Application Service) build라고 불렀다. 실재로 코드가 github에 있어야했고, 서버에서 빌드하는 과정이 필요하다 그리고, Free 모델에서는 Queue에 2시간 대기 하고 나서 빌드하는데 Fail이 발생하면 결과를 최소 2시간 이후에 알 수 있어서 동작하는 버전을 처음 얻는데 까지 이틀 가까이 걸렸다.

🔚 마무리: 확장 가능성과 다음 단계

이 프로젝트는 다음과 같은 방향으로 확장 가능하고 고민해보고 있다:

  • 🔐 다른 사용자 인증 지원: Google account와 같은 OAuth 2.0 연동. 이를 통한 가입의 유연성 증대
    • 📈 요약 품질 개선: Selenium으로 News를 가져오고 Gemini Pro 또는 LLM fine-tuning 도입
  • 🔊 TTS 기능: 요약 내용을 음성으로 읽어주는 기능
  • 📡 푸시 알림: 새로운 뉴스 요약이 도착했을 때 알림 제공

✨ 느낀 점

Cloud 기반 자동화와 모바일 내이티브 앱을 한 프로젝트 안에서 다뤄본 덕분에, 서비스 기획부터 운영까지 전 과정을 경험할 수 있었다. 특히 Pub/Sub, Cloud Function, Firestore를 연결하는 구조가 매우 유연하면서도 확장성 있게 느껴졌다. 나에게도 유용한 기능이어서 나만의 서비스로 활용하고 있다. 조금 더 확장하면 실재로 타인들에게도 공개해서 피드백 받을 수 있는 기능으로 확장도 가능하겠다.

참고 문헌

[1] https://www.inflearn.com/community/questions/1581981/expo-%EA%B0%80-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EB%90%98%EB%A9%B4%EC%84%9C-%EB%AC%B8%EC%A0%9C%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%95%9C%EA%B1%B0%EA%B0%99%EC%8A%B5%EB%8B%88%EB%8B%A4?srsltid=AfmBOopaM9vHkoH-TpxDm6dFG6jVK585QCe4zMDbJ_LatRew-t_iiVP1
[2] https://github.com/blcktgr73/GCPNewsPortal

✅ 프로젝트 개요

  • 목표: Google News 기반 뉴스 수집 및 요약 앱
  • 핵심 가치 (PCA 관점):
    • 고가용성 설계
    • CI/CD 파이프라인 구축 (GitHub Actions + Cloud Build)
    • 비용 효율적인 서버리스 아키텍처

🏗️ 아키텍처 구성도

+--------------------+           +--------------------+
|  User Frontend     | <-------> | Firebase Hosting   |
|  (React Native)    |           | + Firebase Auth    |
+--------------------+           +--------------------+
                                        |
                                        v
                                +------------------+
                                | Firestore (DB)   |
                                +------------------+
                                        ^
                                        |
+----------------------+       +---------------------------+
| Cloud Scheduler      | ----> | Cloud Functions           |
| (매일 크롤링 트리거)  |       | (뉴스 크롤링/요약 실행)     |
+----------------------+       +---------------------------+
                                        |
                                        v
                            +---------------------------+
                            | Cloud Run (LLM API 연동) |
                            +---------------------------+

⚙️ 각 컴포넌트 설명 및 역할

컴포넌트 역할 PCA 고려 포인트
Firebase Hosting 프론트엔드 정적 호스팅 글로벌 CDN 활용으로 고가용성 확보
Firebase Authentication 로그인 및 인증 처리 OAuth2, Anonymous 등 다양한 인증 방식 간편 지원
Firestore 사용자별 뉴스/요약 저장 글로벌 분산 NoSQL, Auto-scaling 및 고가용성 내장
Cloud Functions 뉴스 크롤링 및 요약 자동화 서버리스 처리로 이벤트 기반 트리거 가능 (Cloud Scheduler or HTTP)
Cloud Scheduler 주기적 크롤링 트리거 시간 기반 이벤트, 관리 비용 없음
Cloud Run 뉴스 수집/요약 요청 처리 및 LLM API 호출 서버 (FastAPI 기반) 필요 시만 실행되어 비용 최적화 가능
Cloud Build + GitHub Actions CI/CD 파이프라인 구성 Build/Deploy 자동화, 롤백도 가능

💰 비용 효율 아키텍처 전략

전략 설명
서버리스 기반 Cloud Run, Functions, Firestore 모두 사용량 기반 과금 (idle 시 비용 없음)
Cloud Scheduler + Functions 분리 주기적 트리거만 필요하므로 Compute Engine 등 상시 서버보다 훨씬 저렴
LLM 연동은 Cloud Run에서 제한적으로 사용 필요 시만 호출, API 요금 절감
CI/CD 자동화로 개발 효율 극대화 GitHub Actions → Cloud Build로 배포까지 자동 처리

📌 개발 단계

  1. 뉴스 수집 및 요약 기능
    • FastAPI + Cloud Run
    • Gemini API 연동 + JSON 반환
    • 수동 테스트용 엔드포인트 우선 개발
  2. 사용자 인증 및 저장/조회 기능
    • React Native + Firebase Auth
    • Firestore에 요약 저장, 조회 UI 구현
  3. 자동화 및 고도화
    • Cloud Scheduler + Functions로 주기적 호출
    • Slack 또는 Email 연동 통한 실패 알림 추가 가능

🧱 공통 포함 사항

  • Firestore 스키마 설계
users/
  {userId}/
    summaries/
      {summaryId}: {
        "url": "https://news.example.com",
        "title": "기사 제목",
        "summary": "요약된 본문",
        "createdAt": "2025-06-21T07:30:00Z",
        "source": "Google",
        "category": "Technology",
        "keywords": ["AI", "Google", "ChatGPT"],
        "summaryTokens": 156
      }
  • 필드 설명
필드명 타입 설명
url string 요약한 뉴스의 원본 링크
title string 뉴스 제목
summary string 요약된 본문 내용
createdAt timestamp Firestore 서버 시간 기반 생성일
source string 뉴스 출처 (예: Google, Naver, Daum)
category string 뉴스 카테고리 (예: Technology, Politics, Health)
keywords array Gemini 또는 기타 LLM이 추출한 키워드 리스트
summaryTokens number 요약된 텍스트의 토큰 수 (LLM 비용 추적용)
  • 기술 요약 테이블
항목 기술/서비스 설명
뉴스 수집 Python (requests, BeautifulSoup) 주요 뉴스 사이트 크롤링
요약 처리 Gemini Free API(2.5 Flash) 텍스트 요약 모델 호출
API 서버 Cloud Run (FastAPI) REST API 형태로 제공
인증 Firebase Authentication Google, 이메일 등 연동
저장소 Firestore 요약 결과 사용자별 저장
모바일 앱 React Native (Expo) iOS/Android 앱 구현
스케줄링 Cloud Scheduler + Functions 뉴스 자동 수집/요약 트리거
CI/CD GitHub Actions + Cloud Build 자동 테스트/배포 파이프라인

마무리

이 작업은 요즘 인기 있는 Firebase Only로 고민해 보는 것도 좋겠지만, 처음에 적었 듯 Google Cloud Platform으로 서비스를 구성해보는 것이 목표였다.

뉴스 요약을 시작으로 한 것도 기존에 NAS에서 Google News에서 검색하고 GPT로 요약한 것을 Obisidan에 연동하는 기능이 이미 구현된 것이 있어서 Cloud와 React Native, 특히 모바일에 집중해서 학습이 가능할 것으로 보였다.

이 후에는 Bacekend에 API 설계 구현하고 News Summary를 저장하는 것을 먼저 하게 될 것 같다. 좀 더 익숙하기도 하고, 이 후에는 React Native에 집중해서 학습 할 수도 있을 것 같고 좀 더 편할 것 같기도 하다.

 

참고 링크

https://github.com/blcktgr73/GCPNewsPortal

 

TL;DR
Google Cloud Platform Professional Cloud Architect(PCA) 시험 Pass!
집에서 세팅하기 싫어서 오프라인 시험 장소가서 시험봄.
시험 준비는 Dump가 최고인 듯. 틀린 문제 반복은 Anki.

 

시험 경험

강남의 SRTC에 가서 시험보았다. 집에서 한시간 이상 걸리는 거리였지만, 집에서 설정하고 하는 것이 더 번거롭고 마침 서울에서 다른 일도 보기로 했다.

입장이 매우 깐깐했다.
시험 신청 싸이트에서 이메일로 전송하여 받은 시험번호 표시된 이메일 출력본
여권과 신용카드를 신분증 2개로 보여주었다.
시험 장소 들어가면 휴대폰 끄고 악세사리도 제거해달라고 하고 주머니를 모두 비웠다.

시험 시작하고 초반에 10문제 가까이 sample project문제 나와서 에너지가 많이 들었다. 하지만 실재로는 마지막 10분에 문제 잘 복기해서, 답을 찾았던 것 같았다. 마킹했던 7문제에서 4~5문제를 다시 수정했고, 마킹을 지워서 Review All할 때 마킹한 문제가 없어질때까지 반복했다. 완료 후, 최종 Submit 눌렀었다.

중간에 고민했을 때, 2 시간의 배분을 고민했었다. 총 50문제니, 12~13문제 30분 안에 풀면 되는 거여서 페이스를 잘 유지하기도 하고 시간도 잘 사용했던 같다. 특히, 눈이 아파서 3번 정도 눈 감고 심호흡 6~7번 해서 잘 마무리 한 것 같다.

후반에서야 문제를 특히 꼼꼼히 읽었던 것같다. Dump 문제 풀 때에도 실수 많이 하던 것 중에 하나는 문제를 꼼꼼히 읽지 않아서 키워드를 놓치는 경우가 있었다. 한문장씩, 키워드를 찾아보고 보기에서 지워 가는 형식으로 문제를 풀었다. 아까 이야기 했듯이 복기 하는 시간이 10분 정도 소요 되었다.

시험 준비

학습의 피드백을 많이 받기 위해서 기출 문제 모음으로 볼 수 있는 Dump를 풀어보기를 권한다. 물론 Cloud를 잘 모르는 상태에서 Dump 부터 풀면 정말 힘들 수도 있다. 하지만, Dump를 풀면서 아는 범위를 늘려가는게 한달 이상씩 전체를 훝는 Cloud 강의를 보면서 멍하게 시간 보내는 것 보다는 더 알찰 것 같다.

이러한 결론이 나온거는 24년 12월 부터 이런 저런 강의 사이트 보면서 시간만 보냈었다. 하지만, 기억나는 것은 그리 많지 않아서 더 그렇다.

25년 2월 부터, 본격 Dump 보기 시작한 듯 하다. PCA 문제가 조금 알게 되면 햇갈리는 보기 2개 정도에서 왔다 갔다 하는 문제가 생기기 시작한다. 정말 햇갈리게 하는 문제가 많다. 찾아 보고, 왜그런지 확인해야 한다. ChatGPT도 잘 맞추기는 하지만 Dump 답과 비교해보면 틀리기도 한다. 할루시네이션이라고 하기 보다는 문제가 정말 햇갈리는 문제 같다.

틀린 문제 반복은 Anki를 활용했다. 잊을만하면 나오고 또 틀리기도 하고, Dump Udemy에 있는 것 6개 다 풀고 Anki에 오답노트 햇갈리는 문제 등록해 놓고 반복을 했다. 4월 목표였지만, 결국은 5월 말로 신청했고 걱정도 많이 했지만, 결국은 통과!

이후는 어떻게?

문제가 조금씩 나올거라 학습을 위해서 Anki는 반복할까 한다. 2년 유효하니 지켜보자. 

+ Recent posts