← 전체 글로 돌아가기

서버 운영

빌드는 로컬에서 성공하는데 서버에서만 실패한다

환경변수 설정 문제로 보이지만, 실제는 빌드 타임과 런타임의 구분이 필요한 경우들.

로컬과 서버의 환경이 정말 같은가

빌드 실패는 대개 환경 변수 차이에서 온다. 하지만 같은 변수여도 타이밍이 다르면 다르게 작동한다.

로컬에서 재현할 수 없다면, 먼저 서버의 빌드 로그를 본다.

# 서버에서 직접 빌드
ssh user@server
cd /path/to/project
env | grep -E "NODE|ENV|API" | sort

환경 변수 목록을 로컬과 비교한다.

시간대 환경이 다를 수 있다

# 타임존 확인
date
TZ=UTC date

# 빌드 스크립트에서
echo $TZ

빌드 중 타임스탬프를 생성하거나 시간 기반 조건을 사용한다면, 타임존이 다르면 결과가 달라진다.

권한 확인

# 빌드를 실행하는 사용자
whoami

# 파일 권한
ls -la /path/to/project

# 특정 디렉터리 쓰기 권한
touch /path/to/project/test.txt && rm test.txt

로컬에선 root로 실행해도 서버에선 dockeruser나 ubuntu 같은 다른 계정일 수 있다. 권한이 부족하면 임시 파일을 못 만든다.

포트나 네트워크 상태

빌드 중 외부 서비스에 접근한다면, 서버의 네트워크가 제약될 수 있다.

# 특정 호스트 접근 가능 여부
curl -I https://api.example.com

# DNS 해석
nslookup api.example.com

# 포트 지정 연결
telnet api.example.com 443

특히 외부 npm 패키지 레지스트리, CDN, API 서버에 접근한다면 통신 상태를 먼저 확인한다.

빌드 타임 vs 런타임 환경변수

Next.js, Webpack, Vite 등 많은 빌드 도구는 빌드 타임에 환경 변수를 embed한다.

// next.config.js
module.exports = {
  env: {
    API_URL: process.env.API_URL,
  },
};

이렇게 하면 process.env.API_URL이 빌드 시점의 값으로 고정된다. 배포 후 환경 변수를 바꿔도 소용없다.

# 빌드된 JavaScript를 보면 URL이 하드코딩되어 있다
grep -r "https://api." .next/static/

디스크 공간 확인

df -h
du -sh /path/to/project

서버 디스크가 80% 이상 찼다면 빌드 중간 파일을 만들 수 없다. 특히 node_modules 생성 중 실패할 수 있다.

종속성 버전 문제

Package-lock.json이나 yarn.lock이 없다면, 매번 다른 버전이 설치될 수 있다.

ls -la package-lock.json
ls -la yarn.lock

버전 고정이 없으면 새로운 마이너 버전이 올라왔을 때 호환성 문제가 생긴다.

서버에서 직접 빌드해본다

ssh user@server
cd /path/to/project

# 환경 변수 명시
export NODE_ENV=production
export API_URL=https://api.example.com

# 빌드
npm ci  # package-lock.json 기준으로 정확히 설치
npm run build

Error가 정확히 뭔지 보면 해결 방법이 명확해진다.

최종 체크리스트

배포 전에 확인한다.

  1. 환경 변수가 빌드 타임에 필요한가, 런타임에 필요한가
  2. Package-lock.json이 체크인되어 있는가
  3. 서버의 디스크 공간이 충분한가
  4. 네트워크 접근성이 있는가
  5. 빌드 스크립트가 서버의 권한으로 실행될 때 문제가 없는가

이 다섯 가지를 모두 확인하면 빌드 실패의 대부분을 예방할 수 있다.