개발 기록 2026-10-25 docs/devlog/2026-10-25-2-배포-준비.md GitHub에서 보기

배포 준비: Render Free + Neon Free (WBS 5.1, 5.4)

목표

사용자가 선택지 네 개 (A Render + Neon, B Fly.io + Neon, C 로컬 + 터널, D 배포 없이 공개 저장소와 GIF) 중 A 를 골랐다. 사용자가 계정을 만들고 값 세 개만 넣으면 외부에서 데모가 열리도록 저장소를 준비한다.

한 일

  • mix phx.gen.release --docker 로 Dockerfile, .dockerignore, lib/tickmark/release.ex, rel/overlays/bin/{server,migrate} 생성.
  • Dockerfile: 문서 위키 원본 (docs/, README.md, TASKS.md, AGENTS.md) 을 mix compile 전에 복사. CMD 를 새 bin/start (migrate 뒤 server) 로.
  • .dockerignore: .env, .env.*, .claude/ 를 빌드 컨텍스트에서 제외.
  • 시드 로직을 priv/repo/seeds.exs 에서 Tickmark.Demo 로 옮김. seeds.exs 는 결과만 출력하는 얇은 호출부. 대기 한도를 2분에서 단계당 15분으로 늘림 (0.1 CPU).
  • Tickmark.Application: :seed_demo_on_boot 가 켜져 있으면 Endpoint 뒤에 시드 Task 를 붙임.
  • config/runtime.exs: Repo ssl 기본 켬 (DATABASE_SSL=false 로만 끔), host 에 RENDER_EXTERNAL_HOSTNAME 대체값, :demo_password, :seed_demo_on_boot.
  • config/prod.exs: 메일러를 Swoosh.Adapters.Logger 로. 로그인과 회원가입 화면에 #mail-off-notice 안내.
  • render.yaml Blueprint.
  • 테스트 2개 파일: test/tickmark/demo_test.exs (inline Oban 으로 전체 시드, 비밀번호 로그인, 두 번째 호출 :exists), test/tickmark_web/live/user_live/mail_off_notice_test.exs.
  • 배포 가이드 문서, 검토 문서 상태, WBS, TASKS, README 갱신.

결정과 트레이드오프

  • 부팅 때 시드 vs 수동 시드: 무료 플랜은 셸과 one-off job 이 없어 mix run priv/repo/seeds.exs 를 돌릴 곳이 없다. 대안은 (1) 로컬에서 Neon 에 직접 시드, (2) 부팅 Task. (1) 은 사용자가 운영 DB 주소를 로컬 셸에 넣어야 하고 순서를 틀리기 쉽다. (2) 를 고르고 환경 변수 하나로 켜고 끄게 했다. 이미 있으면 바로 끝나므로 매 부팅 비용은 쿼리 한 번이다.
  • 시드 Task 위치: Oban 이 job 을 돌려야 시드가 끝나므로 Oban 뒤, 그리고 시드가 오래 걸려도 요청을 받도록 Endpoint 뒤에 둔다. Task 의 기본 restart 는 :temporary 라 실패해도 한 번 로그에 남고 재시작을 반복하지 않는다. 반쯤 만든 데이터가 생기면 다음 부팅은 작성자가 있어 건너뛴다. 이때는 DB 를 다시 만든다 (가이드 6장).
  • 메일: 운영 메일러를 붙이려면 발신 도메인과 API 키가 필요하다. 지원 마감일이라 붙이지 않고 비밀번호 로그인만 쓴다. 생성 기본값 (Swoosh.Adapters.Local + local: false) 은 저장 프로세스가 없어 메일 보낼 때 실패하므로 Logger 로 바꿨다. Logger 어댑터의 기본값 (log_full_email: false) 은 수신자만 남겨 로그인 토큰이 로그에 나오지 않는다.
  • SSL: Postgrex 0.22 의 ssl: true 는 시스템 CA 로 인증서와 호스트 이름을 검증한다. 끄는 스위치는 로컬 DB 로 이미지를 돌릴 때만 쓴다.
  • SECRET_KEY_BASE 생성: Render generateValue 는 44자라 Phoenix 의 64바이트 요구보다 짧다. 사용자가 mix phx.gen.secret 으로 만든다.
  • IPv6 바인딩: 생성 기본값 ip: {0, 0, 0, 0, 0, 0, 0, 0} 을 그대로 뒀다. Render 의 Phoenix 가이드는 바인딩 주소를 언급하지 않아 확인 필요로 둔다. 실패하면 IPv4 로 바꾸는 방법을 가이드에 적었다.

막힌 점과 해결

  • Docker 데몬 없음: Colima 가 있지만 꺼져 있다. 이미지 빌드 대신 같은 prod release 를 저장소 사본 (스크래치 디렉터리) 에서 빌드했다. 저장소에서 바로 mix assets.deploy 를 돌리면 priv/static 에 digest 파일이 생겨 작업 트리가 더러워지기 때문이다.
  • CSRF 403: curl 로그인 확인에서 403. 원인은 내 grep 이 name=... value= 순서를 가정했는데 실제 태그는 name type hidden value 순서여서 토큰이 빈 값이었다. 앱 문제가 아니었다.
  • rpc 로 보낸 메일 로그가 안 보임: bin/tickmark rpc 안의 Logger 출력이 노드 로그에 나오지 않았다. rpc 프로세스의 group leader 를 :user 로 바꾸니 New email delivered to kim.writer@example.com 이 보였다. 링크 문자열은 0건.
  • mix phx.gen.secret 16 실패: 32자 미만은 거부한다. 확인용 비밀번호는 openssl rand 로 만들고 파일 권한 600 으로 두었다가 지웠다.

실행 결과

  • set -o pipefail && mix precommit: 588 통과.
  • 로컬 prod release: 첫 부팅 마이그레이션과 시드, 두 번째 부팅 건너뜀, 비밀번호 로그인 302 뒤 /engagements 에 누리정밀, /docs 200, 시드 중 최대 RSS 약 291 MB. 세부는 배포 가이드 5장.
  • mix run priv/repo/seeds.exs (개발 DB): seeds: demo data already exists, nothing to do.

Elixir 학습 포인트

  • Release 와 runtime.exs: release 는 컴파일된 BEAM 파일과 ERTS 를 묶은 것이라 Mix 가 없다. 그래서 마이그레이션은 mix ecto.migrate 가 아니라 Tickmark.Release.migrate/0 를 bin/tickmark eval 로 부른다. config/runtime.exs 는 release 가 시작할 때마다 읽혀 환경 변수를 그때 반영하고, prod.exs 는 빌드할 때 굳는다. 메일러 어댑터는 빌드 시점에, DB 주소는 부팅 시점에 정해지는 이유다.
  • Supervisor 자식으로 Task: {Task, fun} 을 자식 목록에 넣으면 앱이 뜰 때 한 번 실행된다. 자식은 순서대로 시작되므로 목록 뒤에 둔 Task 는 Repo, Oban, Endpoint 가 이미 떠 있다고 믿을 수 있다. :temporary 라 끝나거나 죽어도 다시 시작하지 않는다.
  • Oban 테스트 모드: 테스트 설정은 testing: :manual 이라 job 이 쌓이기만 한다. Oban.Testing.with_testing_mode(:inline, fun) 은 그 함수 안에서만 job 을 넣는 즉시 같은 프로세스에서 실행해, 폴링하는 시드 코드를 그대로 시험할 수 있다.

다음 할 일

  • 사용자: PR 병합, Neon 과 Render 계정, 값 세 개 입력 (가이드 4장).
  • 메인: 배포 로그와 외부 접속 확인 (5.4 완료 표기), 무료 Docker 가 막히면 대안 결정.
  • 남은 일: 라운드 4b 나머지, S3 브라우저 확인, FN/IF 교차 링크, 데모 GIF.

관련 커밋

  • 4c68185 feat(accounts): 운영 메일러를 Logger 로 바꾸고 로그인 화면에 안내 추가
  • 6f2b91e feat(demo): 시드를 Tickmark.Demo 로 옮기고 부팅 시드 추가
  • 92deb09 chore(deploy): release 와 Dockerfile, Render Blueprint 추가
  • 이 devlog 와 문서 갱신은 뒤이은 docs 커밋