배포 준비: 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: Repossl기본 켬 (DATABASE_SSL=false로만 끔),host에RENDER_EXTERNAL_HOSTNAME대체값,:demo_password,:seed_demo_on_boot.config/prod.exs: 메일러를Swoosh.Adapters.Logger로. 로그인과 회원가입 화면에#mail-off-notice안내.render.yamlBlueprint.- 테스트 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에 누리정밀,/docs200, 시드 중 최대 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 커밋