「차고」 비유를 걷어내고, 팀 위키에 문서화된 실제 서버 인프라를 그대로 옮겼습니다. 개발/운영 2박스 체제, 한 박스에서 수십 개 서비스를 격리해 돌리는 멀티테넌트, 명령 한 번의 도메인·HTTPS 프로비저닝, 3계층 백업, 그리고 AI가 스스로 진단하는 자율 운영까지 — 근거는 모두 운영 문서입니다.
2026-05-27부터 개발 박스와 운영 박스를 물리적으로 분리했습니다. 신규 운영 서비스는 운영 박스에 올리고, 개발 박스에 남은 레거시는 순차적으로 이전합니다. 두 박스는 스택 버전을 일치시켜, 개발에서 검증한 그대로 운영에 올립니다.
| 항목 | 개발 박스 (standarda-dev) | 운영 박스 (standarda-prod) |
|---|---|---|
| 클라우드 | AWS EC2 · ap-northeast-2 (서울) · Elastic IP | AWS EC2 · ap-northeast-2 (서울) · Elastic IP |
| 인스턴스 | m5 계열 (근무시간 8 vCPU/32GB ↔ 야간 4 vCPU/16GB 자동 전환) | m6i.xlarge — 4 vCPU / 16GB (2026-08-31 상향) |
| 스토리지 | gp3 EBS | gp3 200GB 암호화 + swap 8GB |
| 역할 | 모든 개발 작업 + 잔류 레거시 운영 | 신규 운영 전용 · 24/7 |
| OS · 런타임 | Ubuntu 22.04 LTS · Python 3.11 · PostgreSQL 14 · Redis 6.0 · Apache 2.4 (+mod_proxy) — 두 박스 버전 일치 | |
| 시간대 | Asia/Seoul (KST, +0900) — 2026-07-09 UTC에서 전환, 로그·타임스탬프 한국 시간 | |
프로젝트마다 ① 독립 실행환경(venv) ② 전용 데이터베이스 ③ 내부 포트를 갖습니다. 포트 대역은 8000–8199이며(2026-08-05 확장), 이 포트는 서버 밖으로 열리지 않고 VPC 내부에서만 열립니다. 외부는 Apache HTTPS 관문만 통합니다.
프로젝트별 venv · 전용 PostgreSQL DB(<proj>/<proj>_prod) · 내부 포트(80xx). 한 서비스의 의존성·데이터·프로세스가 다른 서비스와 섞이지 않습니다.
같은 제품 코드를 고객사마다 별도 인스턴스·전용 DB·전용 도메인으로 띄웁니다(instance-per-company). 코드는 공유하되 데이터·접속은 완전히 분리 — "우리 데이터가 다른 고객과 섞이나?"에 대한 구조적 답입니다.
동일 코드를 개발자마다 자기 포트·도메인·DB로 띄워, 서로의 실험이 부딪히지 않습니다(instance-per-developer).
8000–8199는 VPC 내부(172.31.0.0/16)에서만 열리고, 외부 트래픽은 오직 Apache 443만 통합니다. 서비스 포트는 인터넷에 직접 노출되지 않습니다.
서비스마다 systemd MemoryMax(기본 3G, 무거운 이미지/비전 워크로드 4G)를 걸어, 한 서비스가 폭주해도 자기 캡에서만 잘리고 박스 전체는 살아 있습니다. "예산이 아니라 차단기" — 넉넉히 잡아 오탐 없이 진짜 폭주만 끊습니다.
모든 포트·프로젝트·도메인·배포 위치가 팀 위키 포트표 한 곳에 등재됩니다. 새 프로젝트는 다음 빈 포트를 받아 자동 등록됩니다.
개발 서버를 외부 HTTPS로 여는 전 과정이 하나의 스크립트로 자동화돼 있습니다. Claude Code 세션에서 /dev 슬래시 커맨드를 부르면 아래가 순서대로 실행됩니다. 재실행해도 안전한 멱등 설계입니다.
8000–8199에서 빈 포트를 찾아 배정하고 프로젝트 문서·위키 포트표에 기록합니다.
AWS Route53에 <proj>-dev.popupstudio.ai A 레코드를 자동 생성합니다.
Apache vhost를 만들어 그 도메인을 127.0.0.1:80xx로 잇는 리버스 프록시를 설정합니다.
Let's Encrypt(certbot)로 HTTPS 인증서를 발급 — 처음부터 보안 접속입니다.
서비스를 <proj>-dev.service systemd 유닛으로 등록해 재부팅 후에도 살아 있게 합니다.
운영 배포는 "누가·무엇을·언제 바꿨는지" 추적되도록 개인 단위로 분리하고, 배포 자체는 검증 관문을 통과해야 반영됩니다.
신규 운영은 공유 계정이 아니라 배포자 본인 홈(/home/<user>/<proj>/)에 올립니다. 서비스는 User=<user>, DB는 개인 OWNER, 홈 권한은 0751(Apache 정적 서빙은 되되 목록은 차단). SSH·sudo·git·PostgreSQL이 모두 개인 계정 단위로 추적됩니다.
/release)① git pull --ff-only 실패 → 중단 ② manage.py check 실패 → 보류 ③ 미적용 마이그레이션 → 사람 확인(y/N) ④ 재시작 후 HTTP 200 헬스체크까지 통과해야 배포 완료. 이상이 있으면 자동으로 멈춥니다.
모든 서비스는 popupstudio.ai 하위 도메인 + HTTPS로 노출됩니다. 발급뿐 아니라 갱신까지 무인으로 돌아, 인증서 만료로 서비스가 끊기지 않게 합니다.
popupstudio.ai 호스팅 존에 A 레코드를 CLI로 자동 생성하고 전파를 확인합니다. 등록 도메인 기준 Let's Encrypt 발급 한도(주 50개)에 여유가 커, 수십 개 서브도메인도 문제없습니다.
443 HTTPS 요청을 내부 127.0.0.1:80xx로 잇고, 정적 파일은 Alias로 직접 서빙합니다. 외부에 열리는 건 관문 하나뿐입니다.
certbot이 certbot.timer로 매일 자동 점검·갱신합니다. 발급은 한 번, 갱신은 손 안 대도 유지됩니다.
HTTP→HTTPS 리다이렉트가 ACME 갱신 요청까지 삼켜 인증서가 조용히 만료되는 함정을 겪은 뒤, 갱신 경로만 예외 처리하는 표준을 확립했습니다. 겪은 문제는 다시 겪지 않도록 문서로 굳힙니다.
서로 다른 층위의 백업을 매일 자동으로 남기고, DB 덤프를 AMI 스냅샷 10분 전에 찍어 최신 데이터가 이미지에 포함되게 스태거합니다. 장애 범위에 맞춰 가장 빠른 방법으로 되돌립니다.
| 레이어 | 방식 | 주기 (KST) | 보존 | 복원 |
|---|---|---|---|---|
| 서버 전체 이미지 (AMI) | AWS DLM 자동 정책 (Backup=daily 태그) | 매일 03:00 | 7일 | 새 EC2 인스턴스로 통째 복원 (분~수십 분) |
| 디스크 스냅샷 (EBS) | AMI에 포함, 증분 저장 | AMI와 동일 | AMI와 동일 | 볼륨 단위 복원 |
| 데이터베이스 (pg_dump) | 전체 user DB를 .sql.gz로 덤프 (cron) | 매일 02:50 | 7일 | DB 단위 복원, 몇 분 |
운영 박스에도 Backup=daily 태그가 붙어 같은 AMI 정책이 자동 적용되고, DB 덤프 cron은 박스별로 설치돼 있습니다.
7일 지난 백업 폴더는 자동 삭제되고, 백업 로그에 실패 기록이 없는지 월 1회 점검합니다.
실제로 AMI로 새 인스턴스를 띄워 복원되는지 분기 1회 시험합니다 — "있다"와 "된다"는 다르니까요.
백업 파일이 존재한다는 것과, 그 백업으로 서비스가 실제로 되살아난다는 것은 다릅니다. 후자를 정기적으로 확인합니다.
운영 박스의 어떤 서비스가 메모리 캡에 부딪혀 강제 종료되면, 사람이 로그를 뒤지기 전에 AI가 먼저 읽기 전용으로 조사해 진단서를 자동으로 작성합니다. 감지·조사·기록은 무인, 수정만 사람이 승인합니다.
두 박스 모두 매주 토요일 04:00 KST OS 업데이트 + 재부팅을 완전 무인으로 수행합니다(NEEDRESTART_MODE=a). 백업(02:50)·AMI(03:00) 이후 시간대라 충돌하지 않습니다.
개발 박스는 EventBridge→Lambda로 평일 07:30 사양 ↑ / 20:30 ↓, 주말은 축소를 유지합니다. Elastic IP라 IP·DNS는 그대로, 다운타임은 2~5분입니다.
프로젝트를 지우면 남는 DB·인증서·DNS·포트·메모리 같은 잔여 자원을 전용 도구가 교차검증·회수해 서버를 깨끗하게 유지합니다.
특별히 신경 쓰지 않아도 지켜지도록, 격리·암호화·추적을 인프라에 기본으로 심어 두었습니다.
운영 박스 루트 볼륨(gp3)을 암호화해, 저장된 고객 데이터가 물리적으로 유출돼도 읽히지 않습니다.
서비스 포트는 VPC 내부에서만, 외부는 Apache 443만. 클라우드 메타데이터 접근도 IMDSv2로 차단합니다.
비밀번호 로그인을 끄고 공개키만 허용합니다. 운영 박스는 개인 계정 + 본인 키로만 접속됩니다.
팀 전원이 개인 Linux 계정으로 작업하고 운영은 본인 홈에서 돌아, 누가 무엇을 배포·변경했는지 SSH·sudo·git·DB까지 개인 단위로 추적됩니다.
API 키·OAuth는 소스 밖 .env(gitignore)에만 둡니다. 공용 서비스 키는 git 밖 서버 파일에 두고 셋업 스크립트가 필요한 것만 주입 — 실키가 저장소 이력에 남지 않습니다.
devteam은 두 박스 모두 관리자 권한을 갖되, 개인 계정 추적 · 매일 백업 · 배포 안전게이트 · Claude Code 권한 프롬프트로 안전망을 겹쳐 둡니다.
외부 연동은 붙이는 것으로 끝이 아닙니다. 특히 국내 이메일은 "발송 성공(2xx)"과 "받은편지함 도착"이 전혀 다른 문제라, 도달률까지 엔지니어링합니다.
인증메일이 국내(naver/daum/기관)에 안 닿는 문제를 코드 버그가 아닌 발신 IP 평판 문제로 진단하고, 발송 경로를 단계적으로 전환(SendGrid → NCP → Google Workspace SMTP)했습니다. SPF·DKIM·DMARC 정합 + KISA 화이트도메인 등록으로 국내 인박스에 안착시킵니다.
Sheets·Gmail·Drive·Docs를 공통 클라이언트(standarda-core)로 연동합니다. OAuth 토큰 자동 갱신, 스코프 합집합 표준으로 권한 축소 함정을 피합니다.
내부 integration 토큰(REST)으로 백로그 자동 실행·에러 로그 적재 같은 헤드리스 자동화를 돌립니다. 최소 권한(연결된 허브 페이지만) 원칙을 지킵니다.
운영 성숙도는 사고를 안 내는 게 아니라, 사고를 겪은 뒤 같은 사고가 다시 안 나도록 구조를 바꾸는 데서 드러납니다.
사고 → 구조적 방어 → 자동화로 이어지는 진화가 이 인프라의 운영 원칙입니다. 겪은 문제는 위키에 남겨 다음이 반복하지 않게 합니다.