Skip to main content
Summary

배포는 마지막 복사가 아니다

로컬에서 게시판 애플리케이션이 잘 동작해도 아직 서비스가 완성된 것은 아니다. 실제 사용자는 내 컴퓨터가 아니라 인터넷에 연결된 서버로 접속한다. 배포는 로컬의 코드를 서버로 옮기는 작업이지만, 그 의미는 그보다 훨씬 크다. 데이터베이스가 연결되어야 하고, 환경 변수가 안전하게 설정되어야 하며, 큐와 스케줄러가 계속 실행되어야 한다. 오류가 나면 로그로 추적할 수 있어야 하고, 다음 수정 사항도 안전하게 반영할 수 있어야 한다.

이 장에서는 지금까지 만든 Laravel 게시판을 운영 가능한 서비스로 바라보는 방법을 정리한다. 핵심은 한 번 올리는 것이 아니라, 계속 고칠 수 있는 흐름을 만드는 것이다.

배포와 지속적 개선AI 생성 이미지

배포 전 체크리스트

배포 전에 가장 먼저 확인할 것은 코드가 아니라 기준이다. 서버에 무엇이 있어야 하고, 어떤 값이 로컬과 달라져야 하는지 명확해야 한다.

기본 체크리스트는 다음과 같다.

# 의존성 설치
composer install --no-dev --optimize-autoloader
npm ci
npm run build

# 애플리케이션 준비
php artisan migrate --force
php artisan storage:link

# 운영 캐시 생성
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

이 명령을 무조건 한 번에 실행하라는 뜻은 아니다. 어떤 명령은 최초 배포 때만 필요하고, 어떤 명령은 매 배포 때 필요하다. 예를 들어 storage:link는 공개 파일 업로드를 위해 public/storage 링크를 만드는 명령이다. 보통 서버에서 한 번 설정하면 충분하다. 반면 composer install, npm run build, 캐시 재생성은 코드가 바뀔 때마다 필요할 수 있다.

php artisan key:generate도 주의해야 한다. 이 명령은 APP_KEY가 없을 때 새 키를 만들어 주는 명령이다. 처음 서버를 준비할 때는 유용하지만, 운영 중인 서비스에서 배포 때마다 실행하면 안 된다. APP_KEY가 바뀌면 기존 세션이나 암호화된 데이터가 깨질 수 있기 때문이다. 운영 서버에서는 최초 설정 후 같은 키를 안전하게 보관하고 유지해야 한다.

운영 서버의 .env는 로컬과 달라야 한다. 특히 다음 값은 반드시 점검한다.

APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com
APP_KEY=base64:...

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=board
DB_USERNAME=board_user
DB_PASSWORD=secret

QUEUE_CONNECTION=redis
CACHE_STORE=redis
SESSION_DRIVER=redis

APP_DEBUG=false는 중요하다. 운영 환경에서 디버그 화면이 켜져 있으면 파일 경로, 환경 값, 쿼리 정보가 사용자에게 노출될 수 있다. APP_KEY도 반드시 있어야 한다. Laravel은 이 키로 암호화된 쿠키와 여러 보안 기능을 처리한다. 다시 강조하지만, 운영 중인 서비스에서 APP_KEY를 함부로 바꾸면 기존 세션이나 암호화된 데이터가 깨질 수 있으므로 최초 설정 후에는 신중하게 관리해야 한다.

또한 .env 파일은 Git 저장소에 올리지 않는다. 대신 .env.example에는 필요한 변수 이름만 남겨 두고, 실제 값은 배포 플랫폼의 환경 변수 관리 화면이나 서버의 안전한 위치에서 관리한다. 배포 체크리스트에는 Git 저장소, 환경 변수, APP_KEY, 데이터베이스 연결, 스토리지 링크, 파일 권한, HTTPS, 큐, 스케줄러 확인이 함께 들어가야 한다.1

어떤 방식으로 배포할 것인가

Laravel 애플리케이션을 배포하는 방법은 하나가 아니다. 중요한 것은 '가장 멋진 방식'이 아니라 현재 팀과 서비스에 맞는 방식을 고르는 것이다.

Laravel Cloud는 Laravel 애플리케이션을 위한 관리형 호스팅이다. 서버, 빌드, 라우팅, 데이터베이스, 캐시, 워커, 무중단 배포 같은 운영 요소를 플랫폼이 많이 대신 처리한다. 인프라 경험이 적거나 빠르게 서비스를 공개해야 하는 팀에 적합하다. 대신 서버 내부를 세밀하게 제어해야 하는 경우에는 자유도가 부족할 수 있다.2

Laravel Forge는 서버 자체를 대신 빌려 주는 서비스라기보다, DigitalOcean, Vultr, Hetzner, AWS 같은 서버를 Laravel에 맞게 설정하고 관리하기 쉽게 해 주는 도구다. Nginx, PHP, SSL, 큐 워커, 배포 스크립트 등을 한 화면에서 관리할 수 있다. SSH 접근과 서버 제어권이 필요하고, 어느 정도 운영 구조를 직접 이해하고 싶은 경우에 잘 맞는다.2

Laravel Vapor는 AWS Lambda 기반의 서버리스 배포 방식이다. 일반 VPS처럼 오래 실행되는 PHP 서버와 큐 워커를 직접 관리하는 모델이 아니다. 큐 작업도 전통적인 장기 실행 워커가 아니라 AWS SQS와 Lambda를 통해 처리된다. 트래픽이 갑자기 튀는 서비스, AWS 환경을 이미 쓰는 팀, 서버리스 구조에 익숙한 팀에 적합하다. 반대로 단순한 학습용 게시판을 처음 배포하는 단계라면 구조가 다소 어렵게 느껴질 수 있다.3

일반 VPS 직접 배포도 가능하다. Ubuntu 서버에 Nginx, PHP-FPM, Composer, Node.js, MySQL, Redis, Supervisor, Cron을 직접 설치하는 방식이다. 비용은 예측하기 쉽고 제어권은 가장 크지만, 보안 업데이트, 방화벽, 백업, SSL 갱신, 장애 대응까지 직접 책임져야 한다.

처음 배포를 경험하는 독자라면 Laravel Cloud나 Forge가 부담을 줄여 준다. 다만 어떤 도구를 쓰더라도 Laravel 애플리케이션 안에서 일어나는 일은 같다. 요청은 라우트로 들어오고, 컨트롤러와 모델을 지나 데이터베이스를 사용하며, 큐와 스케줄러가 뒤에서 작업을 처리한다. 배포 도구는 이 흐름이 서버에서도 안정적으로 실행되도록 돕는 장치다.

안전한 배포 순서

운영 배포에서 가장 위험한 순간은 새 코드와 예전 코드가 섞일 때다. 예를 들어 새 코드는 posts.summary 컬럼을 사용하지만, 아직 마이그레이션이 실행되지 않았다면 오류가 난다. 반대로 마이그레이션에서 컬럼을 먼저 삭제했는데, 실행 중인 예전 코드가 그 컬럼을 읽고 있다면 그 역시 장애가 된다.

일반적인 배포 순서는 다음처럼 생각하면 된다.

# 1. 새 코드 가져오기
git pull origin main

# 2. PHP 의존성 설치
composer install --no-dev --optimize-autoloader

# 3. 프론트엔드 빌드
npm ci
npm run build

# 4. 데이터베이스 변경
php artisan migrate --force

# 5. 캐시 재생성
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

# 6. 큐 워커 재시작
php artisan queue:restart

migrate --force의 --force는 운영 환경에서 정말 실행할 것인지 묻는 확인 절차를 생략한다. 자동 배포에서는 사람이 터미널 앞에서 대답할 수 없으므로 필요하다. 하지만 위험한 명령이라는 사실은 변하지 않는다. 운영 데이터베이스에 마이그레이션을 적용하기 전에는 백업과 롤백 계획을 확인해야 한다.

큐 워커도 중요하다. 큐 워커는 오래 실행되는 프로세스라서, 새 코드가 배포되어도 예전 코드를 메모리에 들고 있을 수 있다. 그래서 배포 후 php artisan queue:restart로 현재 작업을 마친 워커가 새 코드로 다시 시작하게 만든다. Supervisor나 Forge 같은 도구를 사용한다면 배포 스크립트와 워커 재시작을 연결해 둘 수 있다.

Forge의 무중단 배포는 새 릴리스 디렉터리에 코드를 준비하고, 필요한 단계가 성공한 뒤 심볼릭 링크를 새 릴리스로 바꾸는 방식으로 동작한다. 실패하면 이전 릴리스가 계속 활성 상태로 남기 때문에 배포 중 장애 가능성을 줄일 수 있다. 이때 업로드 파일, 로그, .env처럼 릴리스가 바뀌어도 유지되어야 하는 경로는 공유 경로로 관리해야 한다.4

스케줄러는 별도로 확인해야 한다. VPS에서는 보통 서버의 Cron에 다음 한 줄을 등록한다.

* * * * * cd /var/www/board-app && php artisan schedule:run >> /dev/null 2>&1

이 설정은 Laravel의 스케줄 정의를 매분 확인하게 한다. 서버가 여러 대라면 같은 작업이 중복 실행되지 않도록 한 서버에서만 실행하거나, Laravel의 중복 방지 기능을 함께 검토해야 한다.

캐시도 배포의 일부다. config:cache를 실행하면 .env와 설정 파일이 하나의 캐시로 묶인다. 따라서 운영 환경 변수를 바꾼 뒤에는 캐시를 다시 만들어야 한다. 문제가 생겼을 때는 다음 명령으로 먼저 캐시를 비워 원인을 분리할 수 있다.

php artisan optimize:clear

운영에서 캐시를 비우는 명령은 원인 파악에 도움이 되지만, 그 자체로 문제를 해결했다는 뜻은 아니다. 설정 값을 고쳤다면 다시 config:cache를 실행해 운영 상태에 맞게 캐시를 만들어 두어야 한다.

무중단 배포에서 생각해야 할 것

무중단 배포는 사용자가 접속 중일 때도 서비스 중단을 거의 느끼지 않게 새 버전을 반영하는 방식이다. 하지만 이름만으로 모든 문제가 사라지지는 않는다.

가장 중요한 원칙은 데이터베이스 변경을 새 코드와 예전 코드가 모두 견딜 수 있게 나누는 것이다. 예를 들어 컬럼 이름을 바꾸고 싶다면 한 번의 배포에서 기존 컬럼을 바로 삭제하지 않는다.

  1. 새 컬럼을 추가한다.

  2. 코드가 새 컬럼과 기존 컬럼을 함께 처리하게 한다.

  3. 데이터를 이전한다.

  4. 충분히 확인한 뒤 다음 배포에서 기존 컬럼을 제거한다.

이렇게 번거롭게 나누는 이유는 운영 서버에서는 요청, 큐 작업, 스케줄 작업이 동시에 움직이기 때문이다. 배포는 정지된 파일 교체가 아니라 살아 있는 시스템의 부품을 바꾸는 일에 가깝다.

또 하나 생각할 것은 롤백이다. 코드만 이전 버전으로 되돌리는 것은 비교적 쉽지만, 데이터베이스는 그렇지 않을 때가 많다. 이미 삭제된 컬럼이나 변환된 데이터는 단순히 Git 커밋을 되돌린다고 복구되지 않는다. 그래서 운영 마이그레이션은 "되돌릴 수 있는가"보다 "예전 코드와 새 코드가 함께 버틸 수 있는가"를 먼저 생각하는 편이 안전하다.

CI로 배포 품질 유지하기

배포를 사람이 매번 기억에 의존해 수행하면 언젠가 빠뜨리는 단계가 생긴다. 그래서 테스트와 점검은 자동화해야 한다. GitHub Actions 같은 CI 도구를 사용하면 Pull Request나 main 브랜치 푸시마다 같은 검사를 반복할 수 있다.

간단한 예시는 다음과 같다.

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          extensions: mbstring, intl, pdo_sqlite

      - name: Install PHP dependencies
        run: composer install --prefer-dist --no-interaction --no-progress

      - name: Install Node dependencies
        run: npm ci

      - name: Build assets
        run: npm run build

      - name: Prepare environment
        run: |
          cp .env.example .env
          php artisan key:generate
          touch database/database.sqlite

      - name: Run tests
        env:
          DB_CONNECTION: sqlite
          DB_DATABASE: database/database.sqlite
        run: php artisan test

      - name: Check code style
        run: ./vendor/bin/pint --test

이 설정은 코드를 가져오고, Composer와 npm 의존성을 설치하고, 테스트용 .env를 만든 뒤 Laravel 테스트를 실행한다. 여기서 key:generate는 운영 서버의 키를 바꾸는 명령이 아니라, CI 안에서 테스트용 환경 파일을 만들기 위한 명령이다. pint는 Laravel 프로젝트에서 코드 스타일을 일정하게 유지하는 데 도움이 된다. 프로젝트가 커지면 PHPStan이나 Larastan 같은 정적 분석 도구를 추가할 수 있다. CI에서 테스트, 코드 스타일, 정적 분석, 의존성 보안 점검을 배포 전 단계로 두면 실패한 코드가 운영 서버에 올라갈 가능성을 줄일 수 있다.5

배포까지 자동화할 수도 있다. 예를 들어 테스트가 성공했을 때만 Forge 배포 훅을 호출하거나, 배포 플랫폼의 Git 연동으로 main 브랜치가 갱신될 때 배포를 시작하게 할 수 있다. 중요한 것은 배포 작업이 테스트 작업에 의존해야 한다는 점이다.

deploy:
  needs: test
  if: github.ref == 'refs/heads/main'
  runs-on: ubuntu-latest
  steps:
    - name: Trigger deployment
      run: curl -X POST ${{ secrets.DEPLOY_HOOK_URL }}

실제 프로젝트에서는 여기에 승인 절차, 스테이징 배포, 태그 기반 릴리스, 롤백 전략을 추가할 수 있다. 처음부터 모두 갖출 필요는 없다. 그러나 최소한 테스트가 실패하면 배포하지 않는 기준은 초기에 세워 두는 것이 좋다.

배포 후에도 개발은 계속된다

서비스는 배포 순간 끝나지 않는다. 오히려 그때부터 운영이 시작된다. 로그를 확인하고, 실패한 큐 작업을 살피고, 느린 쿼리를 찾아내고, 사용자 오류를 고친다. 백업이 실제로 복구 가능한지도 확인해야 한다. 모니터링 도구로 응답 상태를 확인하고, 예외 추적 도구로 운영 오류를 빠르게 발견할 수 있다.

운영 점검은 거창한 대시보드부터 시작할 필요가 없다. 처음에는 다음 질문에 답할 수 있으면 된다.

  • 최근 배포가 성공했는가?

  • 오류 로그가 갑자기 늘지 않았는가?

  • 실패한 큐 작업이 쌓이고 있지 않은가?

  • 스케줄 작업이 정해진 시간에 실행되고 있는가?

  • 데이터베이스 백업이 만들어지고, 실제로 복구 가능한가?

  • 느린 쿼리나 느린 요청이 반복되지 않는가?

이 질문에 꾸준히 답하다 보면 개선할 지점이 자연스럽게 보인다. 인덱스를 추가해야 할 수도 있고, 캐시 전략을 바꿔야 할 수도 있으며, 큐로 분리할 작업이 새로 보일 수도 있다. 배포는 끝이 아니라 이런 개선을 안전하게 반복하기 위한 출발점이다.

이 책에서 만든 게시판은 라우트, 컨트롤러, Blade, 마이그레이션, Eloquent, 테스트, 큐, 스케줄러, 성능과 보안을 지나 배포까지 왔다. 각각은 따로 떨어진 기능이 아니다. 사용자의 요청이 안전하게 처리되고, 데이터가 보존되며, 변경 사항이 검증된 뒤 서버에 반영되도록 연결된 하나의 흐름이다.

Laravel을 배운다는 것은 명령어를 외우는 일이 아니다. 요청이 어디로 들어오고, 어떤 코드가 실행되며, 데이터가 어떻게 저장되고, 운영 환경에서 무엇이 계속 살아 있어야 하는지 이해하는 일이다. 이제 로컬에서 동작하는 예제를 넘어, 계속 개선할 수 있는 서비스의 관점으로 Laravel 프로젝트를 바라볼 수 있다.

참고

1How to Deploy a Laravel 12 Application to Production in 2026

2Laravel Cloud vs. Laravel Forge

3Queues - Laravel Vapor

4Deployments - Laravel Forge

5How to Implement a CI/CD Pipeline in Laravel (Step-by-Step) - CODEPAPER

책 목록

라라벨 학습서: 웹 기초부터 배포까지 배우는 Laravel 실전 입문

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기