fika — 개인 폰트 디자이너를 위한 마켓

서비스Live2025-12 ~ 운영 중앱 첫 릴리스까지 4개월

STACKNestJS · TypeORM · PostgreSQL · MinIO · React · Electron + CoreText(native addon) · k3d · ArgoCD · GitHub Actions

개정 이력

  1. 출처에 본문이 참조한 오픈소스의 GitHub 저장소 링크를 추가했다.
  2. 인프라 절의 플랫폼 공통 설명을 개인 플랫폼 문서로 옮기고, fika 에 해당하는 내용만 남겼다.
  3. 헤더와 카드에 걸린 시간 배지를 추가했다.
  4. 작업량만 보여주던 숫자(테스트·커밋·파일 수 등)를 빼고, 문제의 크기와 결과를 보여주는 숫자만 남겼다.
  5. 아키텍처 그림에 서비스 로고를 넣고, 프로토타입 캡처를 가운데로 정렬했다.
  6. 문단형으로 전면 재작성. 팀 노션 기획서·회의록과 커밋 본문을 근거로 시작·판단·운영 사고를 채우고 아키텍처 그림을 추가했다.
  7. 첫 공개. 카드와 웹 화면 두 장.

시작

카페에서였다. 폰트 디자이너인 누나가 흘러가듯 꺼낸 이야기였다. 한글 폰트는 대부분 기업에 묶여 있다. 큰 파운드리가 만들고 구독 번들로 팔며, 소규모 파운드리나 독립 디자이너가 만든 폰트는 설 자리가 좁다. 개인 디자이너가 자기 폰트를 자유롭게 올리고 팔 수 있는 곳이 있으면 더 다양한 폰트가 나올 텐데, 폰트 디자이너를 위한 플랫폼은 없다고.

그 자리에서 AI 를 붙잡고 30분 만에 돌아가는 걸 만들어 보여줬다. 파운드리별로 폰트를 접고 펴고, 토글로 켜고 끄는 macOS 앱이었다. 누나는 자기 아이디어가 그렇게 바로 눈앞에 나올 거라고는 생각을 못 했는지 눈을 번쩍였고, 그게 계기가 됐다. 2025년 12월의 이 프로토타입 이름은 "Kerning City" 였고, 지금 앱의 뼈대가 여기서 나왔다.

Kerning City 프로토타입 (2025-12)
2025-12 프로토타입 "Kerning City". 파운드리별 접기, TTF/OTF 토글, 베타 버전 표시가 이미 있다.

그 뒤로 누나를 필두로 한 3인 팀이 꾸려져 기획과 화면 설계를 맡았고, 개발은 내가 전부 맡았다. 12월 말 팀이 쓴 첫 기획서의 목표는 한 줄이었다. "소규모 파운드리 및 독립 디자이너를 위한 클라우드 SaaS 솔루션." 차별점으로 삼은 건 미완성 폰트를 먼저 공개하고 사용자 피드백을 받아 함께 완성해 가는 구조였다. 폰트 수가 적고 출시 간격이 긴 독립 디자이너에게는 "나도 저 폰트 같이 만들었어" 라는 애착이 곧 마케팅이 된다는 판단이다. 웹의 폰트 필터에 "베타 / 완성" 이 따로 있는 이유다.

운영 목표도 기획서에 적혀 있었다. 최소한의 기능과 최소한의 운영 리소스로 굴러갈 것, 적극적으로 AI 를 활용해 시스템화할 것. 이 두 문장이 뒤의 거의 모든 기술 결정을 제약했다.

기획에 개발자로 들어가다

첫 기획서의 서비스 특징에는 "기술적 보안(DRM)으로 폰트 유출 방지" 가 들어가 있었다. 파운드리를 설득하려면 당연히 필요한 문장이다. 그런데 이게 가능한지부터 확인해야 했다. 2025년 12월부터 1월까지 이미 서비스 중인 폰트 클라우드 에이전트 두 곳을 약식으로 분석했다. 둘 다 일반 사용자가 들어가기 어려운 시스템 권한 폴더에 파일을 두는 수준이었고, 의도를 가진 사람이면 어렵지 않게 꺼낼 수 있었다. 큰 회사들이 이걸 못 해서 안 한 게 아니었다. 운영체제가 폰트를 그리려면 결국 암호화되지 않은 폰트 파일이 필요하다.

그래서 두 가지 방법을 검토했다. 폴더 접근이나 권한 변경을 감지하면 파일을 지우는 방식은, 권한을 바꾸기 전에 폴더째 복사하는 걸 막을 방법이 없어 작업량 대비 소득이 낮았다. 폰트 메타데이터에 로그인한 사용자의 흔적을 심어 유출 시 법적 증거로 쓰는 방식은 유효하지만, 막는 게 아니라 추적하는 수단이다. 결론은 "완벽한 차단은 불가능하다, 대신 회수할 수 있게 만든다" 였고, 이 결론이 뒤의 라이선스 설계가 됐다.

기획 회의에도 개발 관점으로 들어갔다. 1월 첫 회의에서 계정 하나에 PC 를 몇 대까지 허용할지가 논의로 올라왔고, 4월 에이전트 화면기획서에서 데스크톱 2대로 정해졌다. 앱에 "동기화가 실제로 잘 됐는지 확인할 곳" 과 미리보기 켜고 끄기가 필요하다는 요구, 버전 선택과 버전 정보를 분리하자는 결정도 이 회의들에서 나왔다. 팀은 AI 프로토타이핑 도구로 와이어프레임을 직접 돌려 보며 기능을 다듬고, Figma 로 확정한 뒤 개발 요청을 넘겼다. 에이전트 화면기획서는 v1.0(4월 5일), v1.1(4월 11일, SSO 와 기기 제한 추가), v1.2(8월 10일, 오프라인 상태와 이용권 등록 추가)로 이어졌다.

개발 쪽 기록은 개발자가 아닌 팀원도 읽을 수 있게 남겼다. 커밋을 기준으로 날짜별 패치노트를 웹·API·앱·인프라로 나눠 팀 노션에 올렸다. 요구사항은 팀이 기능정의서와 화면기획서로, 진행 상황은 내가 패치노트로 주고받는 구조였다.

무엇을 만들었나

사용자가 보는 건 두 개다. 웹은 폰트 목록에서 미리보기 문장을 직접 바꿔 보고 크기를 조절한 뒤 상세로 들어가 산다. 앱은 메뉴바에 사는 작은 창으로, 산 폰트를 토글 하나로 켜고 끄고 버전을 고른다. 파운드리와 운영자가 보는 관리자 화면이 세 번째다. 팀이 정리한 전체 흐름은 "회원가입 → 폰트 탐색 → 구매·구독 → 에이전트 설치 → 라이선스 검증 → 폰트 설치" 이고, 셋을 하나의 NestJS API 가 잇는다. 폰트 파일·앱 설치 파일·웹폰트는 전부 오브젝트 스토리지에 있으며 API 를 거쳐서만 내려간다. 앱의 첫 릴리스는 2026년 4월 26일 v1.0.0 이었다.

fika 폰트 목록
웹 — 폰트 목록. 완성·베타 필터, 리스트·그리드 전환, 미리보기 문장 입력과 크기 슬라이더.
fika 폰트 상세
웹 — 폰트 상세. 파운드리 링크와 큰 미리보기. 구매하면 앱이 이어받는다.
Fika Agent 폰트 목록미리보기 켠 상태
앱 — 폰트 목록(왼쪽)과 미리보기를 켠 확장 창(오른쪽). 파운드리·폰트명 정렬, 버전 피커, 활성화 토글.
관리자 폰트 조회/수정
관리자 — 폰트 조회/수정. 전시대기·전시중·전시중지, 파운드리 필터, 웨이트 수·베타 배지.

스택은 익숙한 것으로, 대신 경계를 분명히

외주이고 혼자였다. 새 프레임워크를 배우면서 납기를 맞출 수는 없어서 API 는 회사에서 몇 년째 쓰고 있는 NestJS + TypeORM + PostgreSQL, 화면은 React, 데스크톱 앱도 Swift 가 아니라 같은 언어로 갈 수 있는 Electron 으로 갔다. 전부 "이미 손에 익은 것" 이 이유다. 이 선택의 대가는 알고 있었다. Electron 앱은 설치 파일이 180MB 이고 메모리를 더 쓴다.

그래서 익숙함이 못 덮는 곳을 좁게 정했다. 폰트를 시스템에 등록하고 해제하는 일은 JavaScript 로 할 수 없다. 파일을 ~/Library/Fonts 에 복사하는 방법이 있지만 그러면 "끄기" 가 "삭제" 가 되고, 앞의 분석에서 본 것처럼 사용자가 파일을 그대로 들고 나갈 수 있다. 이 한 가지만 node-addon-api 로 만든 네이티브 브릿지에 맡겨 macOS CoreText 의 사용자 범위 등록(CTFontManagerRegisterFontsForURL)을 호출한다. 토글이 곧 등록/해제이고, 관리자 권한이 필요 없으며, 다른 앱에 즉시 반영된다. 나머지는 전부 Electron 안에 있다. 네이티브 코드는 파일 하나, 함수 둘이다.

인프라 — 1인 운영, 유지비 0 이라는 조건

의뢰인의 조건은 기획서의 운영 목표와 같은 이야기였다. 혼자 운영할 수 있을 것, 그리고 유지비가 많이 나오지 않을 것. 회사에서 AWS 를 운영해 봤으니, 매출이 없는 서비스를 AWS 에 올리는 순간 최소 구성으로도 월 200달러 이상이 나간다는 건 계산할 필요도 없었다. 클라우드는 선택지에서 빠졌고, 그러면 남는 건 개인 서버뿐이다. 마침 나는 사이드 프로젝트 때문에 집의 Mac Studio 에 이미 서버를 굴리고 있었다. 개발자인 내가 관리하는 서버가 가장 적합했다기보다, 그것 말고는 없었다.

솔직히 말하면 1인 운영에 유지비 0 은 불가능한 영역이고 욕심이다. 그래도 혈육의 부탁이니 내 서버를 내주되, 조건을 하나 걸었다. 서버가 재부팅되는 것 말고는 다른 문제가 생기지 않아야 한다. 이 조건이 구성을 결정했다.

개인 서버는 어떤 프로젝트가 언제 리소스를 쓸지 모른다. fika 옆에서 다른 사이드 프로젝트가 돌고, 그게 메모리를 다 먹으면 fika 가 죽는다. 그래서 fika 는 여러 앱을 함께 올리는 k3d 단일 노드 클러스터 위에, 앱마다 자원 상한을 걸고 올라가 있다. 출시된 앱만 "졸업" 시켜 따로 뺀다는 생각이고, fika 는 그 첫 졸업 후보다. 이 기반을 왜 이렇게 만들었는지는 개인 플랫폼 문서에 따로 적었다.

fika 에 해당하는 것만 적으면 이렇다. 웹·관리자·API 가 한 호스트를 경로로 나눠 쓴다. dev 환경은 자동으로 갱신되고, 운영에는 dev 에서 확인한 이미지를 승격 커밋으로 올린다. 폰트 파일과 설치 파일은 클러스터 밖 MinIO 에, 데이터는 클러스터 밖 PostgreSQL 에 둔다. 팀의 로드맵에는 2026년 4분기 외부 공개, 2027년 결제 시스템과 브랜드사 독립 판매가 잡혀 있다. 매출이 생겨 클라우드로 옮길 때 클러스터를 세우는 부분만 바꾸면 되게 해 둔 이유다.

fika 아키텍처 — 사용자·앱·관리자 요청이 Mac Studio 의 nginx 와 k3d 클러스터를 거쳐 API 로 가고, 상태는 클러스터 밖 PostgreSQL·MinIO 에 둔다. 배포는 GitHub Actions 와 platform 레포, ArgoCD 가 맡는다.
요청은 nginx → k3d 의 traefik → 앱으로 흐르고, 상태는 클러스터 밖에 둔다. 배포는 이미지 빌드 → platform 레포 태그 커밋 → ArgoCD 동기화 순서다.

발주처 서비스가 개인 인프라 위에 있다는 건 분명한 리스크다. 그 대신 얻은 건 유지비 0 과, 인프라를 코드로 다루는 훈련이 매 프로젝트마다 누적된다는 점이다. "재부팅 말고는 문제가 없어야 한다" 는 조건은 뒤에 "운영하며" 에서 한 번 깨진다.

라이선스 — 막을 수 없으면 회수한다

결론은 기획 단계에서 이미 나 있었다. 완벽한 복사 방지는 목표에서 빼고 "회수할 수 있는가" 를 기준으로 삼는다. 팀의 라이선스 정책은 구독형 데스크톱 라이선스, 즉 결제한 기간 동안 에이전트를 통해서만 쓰는 형태를 먼저 연다. 그렇다면 에이전트가 할 일은 기간이 끝나거나 권한이 회수됐을 때 확실히 내리는 것이다.

설치가 파일 복사가 아니라 등록이라는 점이 출발이다. 여기에 팀 정책인 계정당 데스크톱 2대 제한을 서버에서 강제한다. 에이전트 기기 세션을 따로 기록해, 세 번째 기기로 로그인하면 가장 오래된 세션을 끊고, 끊긴 세션의 요청에는 정책성 401 을 돌려 앱이 강제 로그아웃하게 했다. 앱은 주기적으로 라이선스를 확인해 만료되거나 회수된 폰트를 내린다.

오프라인은 두 단계로 나눴다. 마지막 동기화가 30일 이내면 앱에 들어가 이미 켠 폰트를 계속 쓸 수 있고, 24시간이 지나면 설치·버전 변경 같은 제한 기능만 막고 "라이선스 확인 필요" 를 알린다. 처음엔 30일 하나였던 기준을 팀의 에이전트 정책에 맞춰 진입과 제한 기능으로 갈랐다. 오프라인 카탈로그는 세션과 분리해 암호화 저장하고, 이 캐시가 깨져도 온라인 경로는 절대 깨지지 않게 실패를 삼킨다. 폰트 파일 자체는 숨김 디렉터리·난독화 파일명·권한·접근 감시로 "가벼운 복사 방지" 까지만 하고, 그 한계를 코드 주석에 적어 두었다.

데스크톱 앱의 로그인

4월 화면기획서 v1.1 에서 Google·Apple·Naver 로그인이 요구사항으로 들어왔다. 웹 로그인을 앱에 그대로 옮길 수는 없었다. Electron 창 안에서 OAuth 를 돌리면 Google 이 임베디드 웹뷰를 거부한다. 그렇다고 시스템 브라우저에서 로그인한 뒤 토큰을 fika:// 딥링크에 실어 앱으로 보내면 브라우저 히스토리와 로그에 토큰이 남는다. 로컬 루프백 서버를 띄우는 방법은 포트 충돌과 방화벽 안내가 따라온다.

결론은 OAuth 는 브라우저에서 끝내고, 콜백이 60초짜리 1회용 코드만 딥링크로 앱에 넘기는 것이다. 앱은 그 코드를 API 에 한 번 교환해 세션을 받는다. 앱이 꺼져 있다가 딥링크로 켜지는 콜드스타트, 이미 켜져 있을 때 두 번째 인스턴스로 들어오는 경우를 둘 다 처리했고, 세 제공자가 같은 경로를 탄다. 기기 제한도 이 경로에 걸려 있다.

데이터 모델을 정리한 날

관리자 화면을 실제 API 에 붙이던 9월 초, "웹에는 보이는데 살 수 없는" 상품이 나왔다. 판매 상태, 활성 여부, 전시 상태 세 값이 따로 놀았기 때문이다. 하나로 줄이기 전에 어느 게 진짜인지 확인했다. 판매 상태는 4월 11일에, 전시 상태는 4월 29일에 들어왔고, 공개 웹은 판매 상태를 읽지 않으며, 실데이터 11건 전부 활성 여부와 판매 상태가 1:1 로 겹쳤다. 독립된 의미가 없는 값이었다. 관리자 화면정의서가 다루는 것도 전시 상태 하나였다. 그래서 어드민은 전시대기·전시중·전시중지만 다루고, 전시중으로 올릴 때 나머지 두 값을 같이 맞춘다. 웹은 전시중만 보여주고, 앱은 이미 산 폰트라 전시 상태와 무관하다. 이 규칙은 테스트로 고정했다.

이용권도 비슷한 종류의 문제였다. 같은 사용자가 쿠폰 두 장을 동시에 등록하거나, 등록 중에 운영자가 이용권을 중단하면 기간이 덮어써지거나 유령 권한이 남는다. (사용자, 상품) 유일키를 두고 기간은 누적 연장하며, 구매와 쿠폰 경로가 같은 사용자 잠금을 먼저 잡고 의존 행을 항상 같은 순서로 잠근다. 낙관적 잠금은 운영자 무효화와의 경합을 못 막고, 큐는 이 규모에 과했다. 트랜잭션이 길어지는 대신 정합성을 택했다.

운영하며 겪은 일

인텔 맥에서 안 열린다. 4월 첫 릴리스 노트에 "Apple Silicon 용 설치 파일 제공" 이라고 적었을 만큼 당연하게 여겼던 부분이 9월에 터졌다. 배포 중이던 설치 파일이 인텔 맥에서 실행되지 않았다. 빌드 설정에 아키텍처 지정이 없어 electron-builder 가 GitHub 러너의 호스트, 즉 Apple Silicon 용만 만들고 있었다. universal 로 바꾸자 세 가지가 연달아 드러났다. 빌드 출력 디렉터리와 tsc 산출물이 같은 dist 라 자기 출력을 다시 asar 에 넣고 있었고, 두 아키텍처를 비교하면서 병합이 실패했다. 네이티브 브릿지는 electron-builder 의 재빌드 대상이 아니라 x86_64·arm64 fat 바이너리로 직접 만들어야 했고, 아니면 인텔 맥에서 앱이 기동 즉시 죽었다. 마지막으로 서명 단계가 keychain 잠금 해제에서 죽었는데, 러너 이미지가 올라가며 드러난 electron-builder 버그라 인증서를 워크플로에서 직접 임포트하는 쪽으로 돌았다. v1.1.3 에서 v1.1.5 까지 하루에 세 번 릴리스했다. 서명·공증·업로드가 명령 하나였기 때문에 가능했다.

운영이 멈춘 날. 9월 7일, dev 브랜치에만 올라가 있던 엔티티 변경이 다른 작업과 함께 main 으로 올라갔는데 마이그레이션이 돌지 않아 없는 컬럼을 SELECT 했다. compose 로 배포하던 시절엔 배포 스크립트가 마이그레이션을 돌렸는데 k3d 로 옮기며 빠진 것이다. ArgoCD PreSync 훅으로 워크로드를 바꾸기 전에 스키마를 맞추게 했고, dev 에서 두 번 확인한 뒤 운영에 넣었다. "재부팅 말고는 문제가 없어야 한다" 는 조건이 깨진 유일한 날이다. 원인은 서버가 아니라 내 배포 경로였다.

재검수 69건. 관리자 화면을 목업에서 실 API 로 바꾸자 승인 버튼이 안 눌리는 식의 결함이 메뉴마다 흩어졌다. 고친 것이 다시 깨져도 알 수 없는 게 더 문제였다. 메뉴 순서대로 재검수하며 동작 하나마다 요구사항 키를 발급하고, 서버 규칙은 단위 테스트, 화면 규칙은 DOM 테스트 제목에 그 키를 넣었다. 키 없는 코드와 테스트 없는 키는 커밋 훅이 막는다.

돌아보며

익숙한 스택을 고른 건 맞았다. 기획부터 운영까지 혼자 API·앱·관리자·인프라를 다 끌고 갈 수 있었던 이유다. 폰트 등록 하나 때문에 네이티브 애드온과 fat 바이너리와 universal 빌드를 다 겪었지만, 네이티브 코드를 파일 하나로 좁혀 둔 덕에 그 범위 안에서 끝났다. 가장 잘한 건 기획서의 "DRM" 한 줄을 받아 적지 않고 먼저 검증한 것이다. 그게 없었으면 막을 수 없는 걸 막으려는 코드에 시간을 썼을 것이다.

아쉬운 것도 있다. 디자이너 프로토타입을 페이지 단위로 그대로 옮겨 디자인 정합은 얻었지만 컴포넌트 재사용이 낮아 다음 화면 추가가 무겁다. 개인 인프라는 유지비가 없지만 발주처 서비스가 올라가는 순간 "내 집 전기" 가 SLA 가 된다. 매출이 생기는 시점이 졸업 시점이다. 남은 것은 팀 로드맵의 외부 공개와 결제 시스템, 그리고 입점 심사를 줄이기 위해 기획서가 요구한 폰트 파일 검증 자동화다.

출처

  • apps/fika/{api,web,admin-web,agent} · 플랫폼 apps/fika-*/prod.yaml. 날짜와 경위는 커밋 본문 기준.
  • 팀 노션의 기획서·회의록·에이전트 화면기획서·라이선스 정책·개발 요청서·패치노트.
  • 참조한 오픈소스(GitHub): NestJS · TypeORM · Electron · electron-builder · node-addon-api · React · PostgreSQL · MinIO · k3d · Argo CD · Traefik.
  • 팀원 실명, 수수료·매출 시뮬레이션 수치, 경쟁 서비스 분석의 세부 방법, 결제 정보와 운영 데이터는 싣지 않는다. 관리자 화면 캡처는 검수 당시 목업 데이터다.