CodeIgniter 4로 블로그 만들기 #30 — production 설정과 배포
드디어 마지막 회차입니다. 프로젝트를 만들고 개발 환경으로 전환한 이래, 우리는 줄곧 CI_ENVIRONMENT = development에서 작업해 왔습니다. 디버그 툴바가 켜져 있고, 에러가 상세하게 나오는 편한 환경이었죠.
하지만 운영(production)은 다릅니다. 디버그를 끄고, 설정은 코드가 아니라 .env로 주입해야 합니다. 이번 회차는 그 두 원칙을 지키는 배포 설정과 절차, 그리고 체크리스트를 정리합니다.
준비물 / 목표
- production용
.env템플릿env.production.example - 배포 절차와 체크리스트를 README에 정리
writable/권한 안내- 핵심 원칙: 커밋된 코드의 기본값은 건드리지 않고
.env로 덮는다
1. 설정은 코드가 아니라 .env로 — 왜?
CI4의 설정은 app/Config/*(예: App.php)에 기본값으로 들어 있고, 실행 시 .env가 그 위를 덮습니다. 배포할 때 흔히 저지르는 실수가 app/Config/App.php의 $baseURL 기본값을 운영 도메인으로, 환경을 production으로 직접 고치는 것입니다. 이렇게 하면 두 가지가 깨집니다.
- 개발이 깨집니다. 커밋된 기본값이 production이면, 다른 개발자가 클론했을 때 디버그가 꺼진 채로 시작하게 됩니다.
- 유출 위험이 생깁니다. DB 비밀번호나 암호화 키 같은 비밀값을 코드에 넣으면 저장소에 그대로 커밋되어 버립니다.
그래서 CI4의 정석은 코드의 기본값은 그대로 두고, 운영값은 서버의 .env로만 주입하는 것입니다. 그 주입값을 안내하기 위해 env.production.example이라는 템플릿을 저장소에 둡니다(비밀값은 빈칸으로).
2. env.production.example 템플릿
운영 서버에서 이 파일을 .env로 복사한 뒤 값을 채우는 방식입니다. 비밀값(DB 비밀번호, encryption.key)은 저장소에 올리지 않고 서버의 .env에만 둡니다.
#--------------------------------------------------------------------
# ENVIRONMENT
#--------------------------------------------------------------------
# production 이면 디버그 툴바·상세 에러가 꺼진다(반드시 production).
CI_ENVIRONMENT = production
#--------------------------------------------------------------------
# APP
#--------------------------------------------------------------------
# 실제 서비스 도메인. 끝에 슬래시 포함. HTTPS 권장.
app.baseURL = 'https://example.com/'
# 깨끗한 URL(index.php 제거). 웹 서버 rewrite(public/.htaccess) 와 함께 쓴다.
app.indexPage = ''
# 모든 요청을 HTTPS 로 강제(운영은 true 권장).
app.forceGlobalSecureRequests = true
#--------------------------------------------------------------------
# ENCRYPTION
#--------------------------------------------------------------------
# 세션·CSRF 등에 쓰이는 키. 서버에서 한 번 생성해 채운다:
# php spark key:generate
encryption.key =
#--------------------------------------------------------------------
# DATABASE
#--------------------------------------------------------------------
database.default.hostname = 127.0.0.1
database.default.database = ci4blog
database.default.username = ci4blog
database.default.password =
database.default.DBDriver = MySQLi
database.default.DBPrefix =
database.default.port = 3306
각 설정이 하는 일을 짚어 봅니다.
CI_ENVIRONMENT = production— 이게 이번 회차의 심장입니다. 디버그 툴바와 상세 에러 페이지가 꺼집니다. 운영에서 상세 에러가 노출되면 내부 구조와 경로가 공격자에게 새어 나갑니다.app.baseURL— 실제 서비스 도메인. 끝에 슬래시를 붙이고, HTTPS를 씁니다.app.indexPage = ''— URL에서index.php를 없앱니다. 웹 서버의 rewrite(public/.htaccess)와 함께 동작합니다.app.forceGlobalSecureRequests = true— 모든 요청을 HTTPS로 강제합니다.encryption.key— 세션·CSRF 등에 쓰이는 키. 저장소에는 빈칸으로 두고, 서버에서php spark key:generate로 한 번 생성해 채웁니다. 이 값이 비밀값의 대표입니다.
3. 배포 절차
README에 배포 순서를 명령까지 적어 둡니다. 클론부터 마이그레이션까지 한 흐름입니다.
# 1) 코드 받기 & 운영용 의존성만 설치(dev 패키지 제외)
git pull --ff-only origin main
composer install --no-dev --optimize-autoloader
# 2) 운영 .env 작성
cp env.production.example .env
# app.baseURL = 'https://내도메인/'
# database.default.* = 운영 DB 접속 정보(비밀번호는 .env 에만)
php spark key:generate # encryption.key 생성
# 3) 마이그레이션
php spark migrate --all
# 4) 캐시 정리(설정/라우트 변경 후)
php spark cache:clear
몇 가지 포인트가 있습니다.
composer install --no-dev --optimize-autoloader— 운영에는 개발 의존성(PHPUnit, Faker 등)이 필요 없습니다.--no-dev로 빼고,--optimize-autoloader로 오토로더를 최적화합니다.php spark key:generate— 위에서 빈칸으로 둔encryption.key를 서버에서 생성합니다.php spark migrate --all— 운영 DB에 스키마를 반영합니다.
4. 배포 체크리스트
빠뜨리기 쉬운 것들을 목록으로 남깁니다.
- [ ]
.env의CI_ENVIRONMENT = production— 디버그 툴바·상세 에러 페이지가 꺼졌는지 확인 - [ ]
app.baseURL이 실제 도메인(끝에/)이고 HTTPS인지 - [ ]
app.forceGlobalSecureRequests = true(HTTPS 강제),app.indexPage = ''(깨끗한 URL) - [ ]
encryption.key가 채워졌는지(php spark key:generate) - [ ] 웹 서버의 **document root가
public/**인지 (그 위 디렉터리가 공개되면 안 됨) - [ ]
.env와 운영 비밀값이 저장소·CI에 올라가지 않는지
**document root가 public/**이어야 한다는 항목은 ep01에서 예고했던 그 이야기입니다. 프로젝트 루트를 웹 루트로 잡으면 app/, writable/, .env가 통째로 외부에 노출됩니다. 웹이 바라보는 곳은 오직 public/뿐이어야 합니다.
5. writable/ 권한
writable/ 아래(cache, logs, session, uploads)는 웹 서버 프로세스가 쓸 수 있어야 합니다. 그렇지 않으면 캐시·로그·세션·업로드가 전부 실패합니다.
# 예: 웹 서버가 www-data 인 경우
sudo chown -R www-data:www-data writable/
sudo find writable/ -type d -exec chmod 775 {} \;
sudo find writable/ -type f -exec chmod 664 {} \;
여기서 대표이미지 업로드의 설계가 다시 빛을 봅니다. 업로드 이미지는 웹 루트 밖(writable/uploads/)에 저장되고 Posts::image 컨트롤러로 서빙되므로, writable/가 외부에서 직접 접근되지 않습니다. 쓰기 권한이 필요한 폴더가 웹으로 노출되지 않는다는 뜻입니다.
마무리 — 커밋과 태그
git add .
git commit -m "chore: production 설정과 배포"
git tag ep30
강좌를 마치며
여기까지가 이 강좌의 마지막입니다. 빈 프로젝트 하나로 시작해, 지금은 배포 가능한 블로그가 손 안에 있습니다. 지나온 길을 되짚어 보면:
- 기초 — 프로젝트 생성과 환경, 라우트 → 컨트롤러 → 뷰의 요청 흐름, 뷰 레이아웃과 첫 Feature 테스트.
- 글 CRUD — Model과 Entity로 데이터 계층을 세우고, 목록·상세·작성·수정·삭제를 만들고, N+1 같은 성능 문제를 디버그 툴바로 확인했습니다.
- 인증 — Shield로 로그인을 붙이고, 라우트 필터와 컨트롤러 가드로 쓰기 권한을 지켰습니다.
- 댓글·카테고리·검색 — 관계 데이터와 목록 필터, 검색을 붙였습니다.
- 마크다운·이미지 — 저장은 원문·표시는 변환이라는 원칙과 XSS 차단, 업로드 검증과 썸네일을 다뤘습니다.
- 운영 — 초록 테스트 위에서 리팩터링하고, 안전하게 버전을 올리고, production으로 배포했습니다.
강좌 내내 반복된 습관이 몇 가지 있습니다. 한 회차 = 하나의 커밋 + 하나의 태그라, 언제든 git checkout epNN으로 특정 시점의 완성 상태로 돌아갈 수 있습니다. 테스트를 꾸준히 쌓아 리팩터링과 업그레이드의 안전망으로 삼았습니다. 그리고 저장은 원본, 파생은 매번, 권한은 서버에서 강제, 비밀값은 .env에만 같은 원칙을 실제 코드로 익혔습니다.
이 블로그는 끝이 아니라 출발점입니다. 태그·RSS·이미지 리사이즈 큐·관리자 대시보드·API… 무엇을 더 붙이든, 지금까지 익힌 "회차 = 커밋, 초록 위에서 변경, 설정은 .env" 리듬을 그대로 이어 가면 됩니다. 여기까지 함께해 주셔서 감사합니다.
이번 회차 요약
- 다루는 파일:
env.production.example(신규, production.env템플릿) /README.md(배포 절차·체크리스트·writable/권한 안내)- 핵심: 코드 기본값(
app/Config/*)은 그대로 두고 운영값은.env로 덮는다.CI_ENVIRONMENT = production으로 디버그 비활성화, document root는public/, 비밀값은.env에만.- 강좌 완결: 프로젝트 생성 → 배포까지, 회차 = 커밋 + 태그로 완성한 CI4 블로그.
댓글 0
아직 댓글이 없습니다.