이미지 로딩 중...
AI Generated
2025. 11. 22. · 5 Views
Docker 배포와 CI/CD 완벽 가이드
Docker를 활용한 컨테이너 배포부터 GitHub Actions를 이용한 자동화 파이프라인까지, 초급 개발자도 쉽게 따라할 수 있는 실전 배포 가이드입니다. AWS EC2에 애플리케이션을 배포하고 SSL 인증서까지 적용하는 전 과정을 다룹니다.
목차
- Dockerfile 작성
- Docker Compose 설정
- 멀티 스테이지 빌드
- AWS EC2 배포
- Nginx 리버스 프록시 설정
- SSL 인증서 설정
- GitHub Actions CI/CD 파이프라인
1. Dockerfile 작성
시작하며
여러분이 개발한 애플리케이션을 다른 팀원에게 공유하거나 서버에 배포할 때 "제 컴퓨터에서는 잘 되는데요?"라는 말을 해본 적 있나요? Node.js 버전이 다르거나, 필요한 라이브러리가 설치되어 있지 않아서 애플리케이션이 실행되지 않는 상황은 개발 현장에서 정말 흔합니다.
이런 문제는 개발 환경과 실행 환경의 차이에서 발생합니다. 여러분의 컴퓨터에는 Node.js 18이 설치되어 있지만, 서버에는 Node.js 14가 설치되어 있다면?
또는 필요한 환경 변수가 설정되어 있지 않다면? 이런 환경 차이가 배포 실패의 주요 원인입니다.
바로 이럴 때 필요한 것이 Dockerfile입니다. Dockerfile은 여러분의 애플리케이션이 실행되는 환경을 코드로 정의하는 설계도와 같습니다.
이 설계도만 있으면 누구나, 어디서나 똑같은 환경을 만들어낼 수 있습니다.
개요
간단히 말해서, Dockerfile은 애플리케이션 실행에 필요한 모든 것을 담은 레시피입니다. 마치 요리 레시피가 재료와 조리 순서를 정확히 알려주듯이, Dockerfile은 어떤 운영체제를 사용할지, 어떤 라이브러리를 설치할지, 어떤 명령어를 실행할지를 단계별로 정의합니다.
이 개념이 필요한 이유는 '환경의 일관성'을 보장하기 때문입니다. 개발 환경, 테스트 환경, 운영 환경 모두 동일한 Dockerfile로부터 만들어지면 "제 컴퓨터에서는 되는데" 같은 문제가 사라집니다.
예를 들어, 신입 개발자가 입사 첫날 프로젝트를 실행하려고 할 때, 복잡한 설치 과정 없이 Docker만 설치하면 바로 개발을 시작할 수 있습니다. 전통적인 방법에서는 "README.md를 보고 Node.js 설치, npm install 실행, 환경 변수 설정..." 등 여러 단계를 수동으로 진행했다면, 이제는 "docker build" 명령어 하나로 모든 준비가 완료됩니다.
Dockerfile의 핵심 특징은 첫째, 재현 가능성입니다. 같은 Dockerfile은 언제 어디서 실행하든 똑같은 환경을 만듭니다.
둘째, 버전 관리가 가능합니다. Git으로 Dockerfile을 관리하면 환경 변경 이력을 추적할 수 있습니다.
셋째, 레이어 캐싱으로 빌드 속도를 최적화할 수 있습니다. 이러한 특징들이 현대적인 개발 워크플로우에서 Docker가 필수가 된 이유입니다.
코드 예제
# Node.js 18 기반의 공식 이미지를 기본으로 사용
FROM node:18-alpine
# 작업 디렉토리를 /app으로 설정
WORKDIR /app
# package.json과 package-lock.json을 먼저 복사 (캐싱 최적화)
COPY package*.json ./
# 프로덕션 의존성만 설치
RUN npm ci --only=production
# 소스 코드를 복사
COPY . .
# 애플리케이션이 사용할 포트 명시
EXPOSE 3000
# 컨테이너 실행 시 실행될 명령어
CMD ["node", "server.js"]
설명
이것이 하는 일: 위 Dockerfile은 Node.js 애플리케이션을 실행하기 위한 완전한 환경을 단계별로 구성합니다. FROM부터 CMD까지 각 명령어는 하나의 레이어를 만들며, 이 레이어들이 쌓여서 최종 Docker 이미지가 완성됩니다.
첫 번째로, FROM node:18-alpine은 우리가 만들 이미지의 기반을 정합니다. node:18-alpine은 Node.js 18이 설치된 경량 리눅스 환경입니다.
alpine 버전을 사용하는 이유는 일반 node 이미지가 900MB 이상인 반면, alpine 버전은 100MB 정도로 크기가 훨씬 작아 다운로드와 배포가 빠르기 때문입니다. 그 다음으로, WORKDIR /app과 COPY 명령어들이 실행됩니다.
여기서 중요한 점은 package.json을 먼저 복사하고 npm ci를 실행한 뒤, 나중에 소스 코드를 복사한다는 것입니다. 이렇게 하면 소스 코드가 변경되어도 package.json이 변하지 않았다면 npm ci 단계를 캐시에서 재사용할 수 있어 빌드 시간이 크게 단축됩니다.
마지막으로, EXPOSE 3000은 컨테이너가 3000번 포트를 사용한다고 문서화하는 역할을 하고, CMD는 컨테이너가 시작될 때 node server.js를 실행하도록 지정합니다. RUN과 CMD의 차이점은 RUN은 이미지 빌드 시 실행되고, CMD는 컨테이너가 시작될 때마다 실행된다는 것입니다.
여러분이 이 Dockerfile을 사용하면 개발 팀 누구나 동일한 Node.js 환경에서 애플리케이션을 실행할 수 있습니다. 또한 이미지를 Docker Hub에 올리면 전 세계 어디서든 동일한 환경을 단 몇 초 만에 받아서 실행할 수 있습니다.
보안 측면에서도 alpine 이미지는 불필요한 패키지가 없어 공격 표면이 작고, --only=production 옵션으로 개발 의존성을 제외하여 이미지 크기와 보안 위험을 모두 줄일 수 있습니다.
실전 팁
💡 package.json을 소스 코드보다 먼저 복사하면 의존성이 변경되지 않은 경우 캐시를 활용해 빌드 시간을 10배 이상 단축할 수 있습니다.
💡 alpine 이미지를 사용할 때 일부 네이티브 모듈이 호환되지 않을 수 있으므로, 문제가 발생하면 node:18-slim을 대안으로 고려하세요.
💡 .dockerignore 파일을 만들어 node_modules, .git 등 불필요한 파일을 제외하면 빌드 속도가 빨라지고 이미지 크기도 줄어듭니다.
💡 보안을 위해 root 사용자 대신 일반 사용자를 생성하여 실행하세요. USER node 명령어를 CMD 전에 추가하면 됩니다.
💡 ENV NODE_ENV=production을 추가하면 프레임워크들이 프로덕션 모드로 실행되어 성능이 향상됩니다.
2. Docker Compose 설정
시작하며
여러분이 만든 웹 애플리케이션을 실행하려면 데이터베이스, 캐시 서버, 백엔드 API, 프론트엔드 등 여러 서비스가 함께 동작해야 하는 상황을 경험해보셨나요? 각각을 별도의 터미널에서 실행하고, 포트 번호를 맞추고, 서비스 간 연결 설정을 일일이 하다 보면 실수하기 쉽고 시간도 오래 걸립니다.
이런 문제는 마이크로서비스 아키텍처나 복잡한 애플리케이션에서 특히 심각합니다. "데이터베이스 먼저 시작하고, 2초 기다린 다음 백엔드 시작하고..." 같은 순서를 매번 기억하고 실행하는 것은 비효율적이며, 새로운 팀원이 합류할 때마다 긴 온보딩 시간이 필요합니다.
바로 이럴 때 필요한 것이 Docker Compose입니다. 여러 개의 컨테이너를 하나의 설정 파일로 정의하고, 단 한 번의 명령어로 모든 서비스를 함께 시작하고 관리할 수 있게 해줍니다.
개요
간단히 말해서, Docker Compose는 여러 개의 Docker 컨테이너를 하나의 애플리케이션처럼 관리하는 오케스트레이션 도구입니다. YAML 파일 하나로 전체 애플리케이션 스택을 정의하고, docker-compose up 명령어 하나로 모든 서비스를 동시에 실행할 수 있습니다.
이 개념이 필요한 이유는 복잡한 서비스 간 의존성과 네트워크 설정을 자동화하기 때문입니다. 데이터베이스 컨테이너와 웹 애플리케이션 컨테이너가 서로 통신해야 할 때, Docker Compose는 자동으로 네트워크를 구성하고 서비스 이름으로 통신할 수 있게 해줍니다.
예를 들어, 로컬 개발 환경에서 PostgreSQL, Redis, Node.js API, React 프론트엔드를 모두 함께 실행하고 싶을 때 매우 유용합니다. 전통적인 방법에서는 각 서비스를 별도로 실행하고 IP 주소와 포트를 하드코딩했다면, 이제는 docker-compose.yml에 서비스 이름만 적으면 자동으로 연결됩니다.
Docker Compose의 핵심 특징은 첫째, 선언적 설정입니다. "어떻게" 실행할지가 아니라 "무엇을" 실행할지만 정의하면 됩니다.
둘째, 환경 격리입니다. 프로젝트마다 독립된 네트워크와 볼륨을 사용해 충돌이 없습니다.
셋째, 재현 가능성입니다. docker-compose.yml 파일만 있으면 누구나 동일한 환경을 실행할 수 있습니다.
이러한 특징들이 로컬 개발 환경 구성을 혁신적으로 간소화합니다.
코드 예제
version: '3.8'
services:
# PostgreSQL 데이터베이스 서비스
db:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: qwer1234
POSTGRES_DB: myapp
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432"
# Redis 캐시 서비스
cache:
image: redis:7-alpine
ports:
- "6379:6379"
# Node.js 백엔드 API 서비스
api:
build: ./backend
depends_on:
- db
- cache
environment:
DATABASE_URL: postgresql://postgres:qwer1234@db:5432/myapp
REDIS_URL: redis://cache:6379
ports:
- "3000:3000"
# 데이터 영구 저장을 위한 볼륨
volumes:
db-data:
설명
이것이 하는 일: 위 docker-compose.yml은 PostgreSQL 데이터베이스, Redis 캐시, Node.js API 서버로 구성된 완전한 백엔드 스택을 정의합니다. 각 서비스는 독립된 컨테이너로 실행되지만 자동으로 생성된 네트워크를 통해 서로 통신할 수 있습니다.
첫 번째로, db 서비스는 PostgreSQL 데이터베이스를 정의합니다. environment 섹션에서 데이터베이스 비밀번호와 초기 데이터베이스 이름을 설정하고, volumes를 통해 데이터를 영구 저장합니다.
이렇게 하면 컨테이너를 삭제하고 다시 만들어도 데이터가 보존됩니다. ports는 호스트의 5432 포트를 컨테이너의 5432 포트로 연결해 데이터베이스 클라이언트로 직접 접속할 수 있게 합니다.
그 다음으로, cache와 api 서비스가 정의됩니다. cache는 간단히 Redis 공식 이미지를 사용하고, api는 ./backend 디렉토리의 Dockerfile로 빌드됩니다.
여기서 중요한 것은 depends_on입니다. 이는 api 서비스가 db와 cache가 시작된 후에 시작되도록 순서를 보장합니다.
또한 환경 변수에서 DATABASE_URL에 "db:5432"처럼 서비스 이름을 호스트명으로 사용하는 것을 주목하세요. Docker Compose가 자동으로 DNS를 설정해주기 때문입니다.
마지막으로, volumes 섹션에서 db-data라는 이름있는 볼륨을 정의합니다. 이는 Docker가 관리하는 영구 저장소로, 데이터베이스 파일이 여기에 저장됩니다.
컨테이너를 내리고 다시 올려도 데이터가 유지되며, docker-compose down -v 명령어로 명시적으로 삭제하기 전까지 보존됩니다. 여러분이 이 docker-compose.yml을 사용하면 복잡한 네트워크 설정 없이 백엔드 스택 전체를 한 번에 실행할 수 있습니다.
새로운 개발자는 Docker만 설치하고 docker-compose up -d 명령어 하나로 전체 개발 환경을 구성할 수 있습니다. 또한 각 서비스가 격리되어 있어 포트 충돌이나 버전 충돌 걱정이 없으며, 프로젝트별로 독립적인 환경을 유지할 수 있습니다.
실전 팁
💡 docker-compose up -d로 백그라운드에서 실행하고, docker-compose logs -f api로 특정 서비스의 로그만 실시간으로 확인할 수 있습니다.
💡 .env 파일을 만들어 민감한 정보를 관리하세요. POSTGRES_PASSWORD=${DB_PASSWORD}처럼 참조하면 Git에 비밀번호를 커밋하지 않아도 됩니다.
💡 depends_on은 시작 순서만 보장할 뿐 서비스가 준비될 때까지 기다리지 않습니다. wait-for-it.sh 같은 스크립트로 실제 준비 상태를 확인하세요.
💡 개발 중에는 volumes로 코드 디렉토리를 마운트하면 코드 수정이 즉시 반영되어 컨테이너를 재시작할 필요가 없습니다.
💡 docker-compose down은 컨테이너만 삭제하고, docker-compose down -v는 볼륨까지 삭제합니다. 데이터 유지가 필요하면 -v를 빼고 실행하세요.
3. 멀티 스테이지 빌드
시작하며
여러분이 React나 Vue 같은 프론트엔드 애플리케이션을 Docker 이미지로 만들 때, 빌드 도구와 개발 의존성까지 모두 포함되어 이미지 크기가 1GB를 넘는 경험을 해보셨나요? 실제로 배포할 때는 빌드된 정적 파일만 있으면 되는데, 웹팩, 바벨, TypeScript 컴파일러 등 빌드 도구까지 함께 포함되어 있어 낭비가 심합니다.
이런 문제는 보안과 성능 모두에 악영향을 미칩니다. 이미지 크기가 크면 배포 시간이 길어지고, 불필요한 도구들이 포함되어 있으면 보안 취약점의 위험도 증가합니다.
특히 클라우드 환경에서는 네트워크 전송량이 비용과 직결되므로 이미지 크기 최적화가 매우 중요합니다. 바로 이럴 때 필요한 것이 멀티 스테이지 빌드입니다.
빌드 단계와 실행 단계를 분리하여, 최종 이미지에는 실행에 필요한 파일만 포함시켜 크기를 10분의 1로 줄일 수 있습니다.
개요
간단히 말해서, 멀티 스테이지 빌드는 하나의 Dockerfile 안에서 여러 단계를 거쳐 이미지를 만드는 기법입니다. 첫 번째 스테이지에서 애플리케이션을 빌드하고, 두 번째 스테이지에서는 첫 번째 스테이지에서 만든 결과물만 복사해와서 최종 이미지를 구성합니다.
마치 공장에서 제품을 만든 후 완성품만 포장하여 출하하는 것과 같습니다. 이 개념이 필요한 이유는 빌드 환경과 실행 환경의 요구사항이 다르기 때문입니다.
TypeScript로 작성된 Node.js 애플리케이션을 예로 들면, 빌드 시에는 TypeScript 컴파일러, 각종 빌드 도구가 필요하지만, 실행 시에는 컴파일된 JavaScript 파일만 있으면 됩니다. 예를 들어, Next.js 애플리케이션을 배포할 때 개발 의존성을 제외하면 이미지 크기를 1.2GB에서 150MB로 줄일 수 있습니다.
전통적인 방법에서는 로컬에서 빌드하고 결과물만 Dockerfile에 복사하거나, 큰 이미지를 그대로 사용했다면, 이제는 Dockerfile 하나로 빌드와 최적화를 모두 자동화할 수 있습니다. 멀티 스테이지 빌드의 핵심 특징은 첫째, 이미지 크기 최적화입니다.
불필요한 빌드 도구를 제거하여 크기를 대폭 줄입니다. 둘째, 보안 향상입니다.
빌드 도구의 취약점이 프로덕션 이미지에 포함되지 않습니다. 셋째, 빌드 프로세스의 일관성입니다.
로컬과 CI/CD 모두에서 동일한 Dockerfile로 빌드할 수 있습니다. 이러한 특징들이 현대적인 Docker 이미지 빌드의 표준이 되었습니다.
코드 예제
# 빌드 스테이지: Node.js 전체 환경에서 애플리케이션 빌드
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
# 개발 의존성 포함하여 모든 패키지 설치
RUN npm ci
COPY . .
# 프로덕션 빌드 실행
RUN npm run build
# 실행 스테이지: 경량 이미지에서 빌드 결과물만 실행
FROM node:18-alpine AS runner
WORKDIR /app
# 프로덕션 의존성만 설치
COPY package*.json ./
RUN npm ci --only=production
# 빌드 스테이지에서 빌드된 결과물만 복사
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
설명
이것이 하는 일: 위 Dockerfile은 두 개의 독립적인 스테이지로 구성됩니다. 첫 번째 builder 스테이지에서 애플리케이션을 컴파일하고, 두 번째 runner 스테이지에서는 컴파일된 결과물만 가져와 최종 이미지를 만듭니다.
최종 이미지에는 빌드 도구나 소스 코드가 포함되지 않습니다. 첫 번째로, FROM node:18-alpine AS builder로 시작하는 빌드 스테이지에서는 모든 개발 의존성을 설치하고 애플리케이션을 빌드합니다.
npm ci를 --only=production 없이 실행하여 devDependencies도 모두 설치하는 이유는 TypeScript 컴파일러, 웹팩 등 빌드에 필요한 도구들이 여기에 포함되어 있기 때문입니다. npm run build를 실행하면 /app/dist 디렉토리에 컴파일된 JavaScript 파일이 생성됩니다.
그 다음으로, FROM node:18-alpine AS runner로 시작하는 실행 스테이지에서는 새로운 깨끗한 이미지를 만듭니다. 이 스테이지는 이전 builder 스테이지와 완전히 독립적입니다.
여기서는 프로덕션 의존성만 설치하고(--only=production), COPY --from=builder /app/dist ./dist 명령어로 빌드 스테이지에서 만든 결과물만 복사해옵니다. 이것이 멀티 스테이지 빌드의 핵심입니다.
마지막으로, 최종 이미지는 runner 스테이지만을 포함합니다. Docker는 Dockerfile의 마지막 FROM 이후 내용만 최종 이미지로 만들고, 이전 스테이지들은 빌드 과정에서만 사용되고 버려집니다.
결과적으로 최종 이미지에는 Node.js 런타임, 프로덕션 의존성, 컴파일된 코드만 포함되어 크기가 획기적으로 줄어듭니다. 여러분이 이 멀티 스테이지 빌드를 사용하면 TypeScript 프로젝트의 경우 이미지 크기를 약 70-80% 줄일 수 있습니다.
React나 Vue 같은 SPA를 Nginx로 서빙하는 경우에는 더 극적인데, builder에서 빌드하고 nginx:alpine 이미지에 정적 파일만 복사하면 1GB 이미지를 20MB로 줄일 수 있습니다. 또한 최종 이미지에 컴파일러나 빌드 도구가 없어 공격자가 악용할 수 있는 도구가 제거되어 보안이 강화됩니다.
실전 팁
💡 React/Vue 같은 SPA는 builder에서 빌드하고 nginx:alpine에 정적 파일만 복사하면 이미지 크기를 95% 이상 줄일 수 있습니다.
💡 AS builder, AS runner처럼 스테이지에 이름을 부여하면 가독성이 높아지고 특정 스테이지까지만 빌드할 수도 있습니다(docker build --target builder).
💡 개발용과 프로덕션용 Dockerfile을 따로 관리하지 말고, 멀티 스테이지로 하나의 Dockerfile에서 target 옵션으로 구분하세요.
💡 COPY --from=builder는 이전 스테이지뿐만 아니라 다른 이미지에서도 파일을 복사할 수 있습니다. 예: COPY --from=nginx:latest /etc/nginx/nginx.conf
💡 빌드 캐시를 최대한 활용하려면 자주 변경되는 파일(소스 코드)은 나중에, 거의 변경되지 않는 파일(package.json)은 먼저 COPY하세요.
4. AWS EC2 배포
시작하며
여러분이 로컬에서 개발한 애플리케이션을 실제 사용자가 접속할 수 있는 서버에 올려야 하는데, 어디서부터 시작해야 할지 막막했던 경험이 있나요? 클라우드 서비스는 많은데 어떤 것을 선택해야 할지, 서버는 어떻게 설정해야 할지, 보안은 어떻게 관리해야 할지 고민이 많습니다.
이런 문제는 특히 처음 배포를 경험하는 개발자에게 큰 장벽입니다. 서버 비용 최적화, 보안 그룹 설정, SSH 키 관리, 방화벽 규칙 등 생소한 개념들이 많아 실수하기 쉽고, 잘못 설정하면 보안 사고로 이어질 수 있습니다.
바로 이럴 때 필요한 것이 AWS EC2 배포 지식입니다. EC2는 가장 기본적이면서도 유연한 클라우드 서버 서비스로, Docker와 결합하면 안정적이고 확장 가능한 배포 환경을 구축할 수 있습니다.
개요
간단히 말해서, AWS EC2는 클라우드에서 제공하는 가상 서버입니다. 마치 여러분의 컴퓨터와 같지만 아마존 데이터센터에서 24시간 운영되며, 필요에 따라 성능을 높이거나 낮출 수 있는 유연성을 제공합니다.
이 개념이 필요한 이유는 안정적인 서비스 운영을 위해서입니다. 여러분의 개인 컴퓨터로 서비스를 운영하면 전기가 끊기거나 인터넷이 끊기면 서비스도 중단됩니다.
하지만 EC2는 99.99% 가용성을 보장하는 데이터센터에서 운영되어 안정적입니다. 예를 들어, 스타트업에서 MVP를 빠르게 배포하거나, 개인 프로젝트를 포트폴리오용으로 운영할 때 EC2는 가장 적합한 선택입니다.
전통적인 방법에서는 물리 서버를 직접 구매하고 데이터센터에 입주시켰다면, 이제는 몇 번의 클릭으로 서버를 생성하고 몇 분 안에 배포할 수 있습니다. AWS EC2의 핵심 특징은 첫째, 탄력성입니다.
트래픽이 증가하면 서버를 추가하고, 감소하면 줄일 수 있습니다. 둘째, 비용 효율성입니다.
프리티어로 1년간 무료로 사용하거나, 사용한 만큼만 비용을 지불할 수 있습니다. 셋째, 완전한 제어권입니다.
서버에 SSH로 접속하여 원하는 소프트웨어를 자유롭게 설치하고 설정할 수 있습니다. 이러한 특징들이 EC2를 초보자부터 대기업까지 널리 사용하는 이유입니다.
코드 예제
#!/bin/bash
# EC2 인스턴스에 SSH 접속 후 실행할 배포 스크립트
# Docker 설치 (Ubuntu 기준)
sudo apt-get update
sudo apt-get install -y docker.io docker-compose
# Docker를 sudo 없이 실행할 수 있도록 권한 설정
sudo usermod -aG docker $USER
# Git에서 프로젝트 클론
git clone https://github.com/yourusername/yourproject.git
cd yourproject
# 환경 변수 파일 생성
echo "DATABASE_URL=postgresql://user:qwer1234@localhost:5432/mydb" > .env
echo "NODE_ENV=production" >> .env
# Docker Compose로 전체 스택 실행
docker-compose up -d
# 실행 중인 컨테이너 확인
docker-compose ps
설명
이것이 하는 일: 위 배포 스크립트는 새로운 EC2 인스턴스에 Docker 환경을 구축하고 애플리케이션을 배포하는 전체 과정을 자동화합니다. SSH로 서버에 접속한 후 이 스크립트를 실행하면 개발 환경과 동일한 프로덕션 환경이 몇 분 안에 준비됩니다.
첫 번째로, apt-get으로 Docker와 Docker Compose를 설치합니다. Ubuntu를 기준으로 작성되었으며, Amazon Linux를 사용한다면 yum을 사용해야 합니다.
docker.io는 Docker Engine이고, docker-compose는 여러 컨테이너를 관리하는 도구입니다. 설치 후 usermod 명령어로 현재 사용자를 docker 그룹에 추가하면 매번 sudo를 입력하지 않아도 Docker 명령어를 실행할 수 있습니다.
그 다음으로, Git에서 프로젝트를 클론하고 환경 변수 파일을 생성합니다. .env 파일에는 데이터베이스 연결 정보, API 키 등 민감한 정보가 포함되므로 절대 Git에 커밋하면 안 됩니다.
대신 서버에서 직접 생성하거나 AWS Secrets Manager를 사용하는 것이 안전합니다. NODE_ENV=production은 프로덕션 모드로 실행하여 성능을 최적화하고 디버그 정보 노출을 방지합니다.
마지막으로, docker-compose up -d로 애플리케이션을 백그라운드에서 실행합니다. -d 옵션은 detached 모드로, 터미널을 닫아도 컨테이너가 계속 실행됩니다.
docker-compose ps로 모든 서비스가 정상적으로 실행 중인지 확인할 수 있으며, 문제가 있다면 docker-compose logs를 통해 로그를 확인합니다. 여러분이 이 배포 프로세스를 사용하면 복잡한 서버 설정 없이 애플리케이션을 빠르게 배포할 수 있습니다.
EC2 프리티어를 활용하면 1년간 무료로 운영할 수 있어 개인 프로젝트나 학습용으로 적합합니다. 또한 Auto Scaling과 Load Balancer를 추가하면 트래픽이 증가해도 자동으로 서버를 확장하여 안정적인 서비스를 유지할 수 있습니다.
보안 그룹 설정으로 22번(SSH), 80번(HTTP), 443번(HTTPS) 포트만 열고 나머지는 차단하면 기본적인 보안을 확보할 수 있습니다.
실전 팁
💡 EC2 보안 그룹에서 SSH(22번 포트)는 여러분의 IP에서만 접속 가능하도록 제한하면 무차별 대입 공격을 방지할 수 있습니다.
💡 Elastic IP를 할당하면 서버를 재시작해도 IP 주소가 변경되지 않아 DNS 설정이 간편합니다(단, 사용하지 않는 Elastic IP는 비용이 발생하므로 주의).
💡 프리티어는 t2.micro 인스턴스를 월 750시간 무료로 제공하는데, 이는 한 달 내내 1대를 켜두거나 2대를 절반씩 사용할 수 있는 시간입니다.
💡 배포 스크립트를 실행하기 전에 chmod 400 keypair.pem으로 SSH 키 파일 권한을 설정하지 않으면 접속이 거부됩니다.
💡 비용 절감을 위해 개발/테스트 서버는 근무 시간에만 켜두고, CloudWatch로 스케줄링하여 자동으로 시작/중지하도록 설정하세요.
5. Nginx 리버스 프록시 설정
시작하며
여러분이 Node.js 애플리케이션을 3000번 포트에서 실행하고 있는데, 사용자가 "example.com"으로 접속하면 자동으로 연결되게 하고 싶었던 경험이 있나요? 또는 같은 서버에서 여러 개의 애플리케이션을 실행하면서 도메인이나 경로에 따라 다른 애플리케이션으로 연결하고 싶었나요?
이런 문제는 실무에서 매우 흔합니다. 사용자에게 "example.com:3000"처럼 포트 번호를 알려줄 수는 없고, 여러 애플리케이션이 각자 다른 포트를 사용하면 관리가 복잡해집니다.
또한 Node.js 같은 애플리케이션 서버를 직접 인터넷에 노출하면 보안과 성능 면에서 취약점이 생깁니다. 바로 이럴 때 필요한 것이 Nginx 리버스 프록시입니다.
사용자 요청을 받아 적절한 백엔드 서버로 전달하고, 정적 파일 서빙, SSL 처리, 로드 밸런싱까지 담당하는 게이트웨이 역할을 합니다.
개요
간단히 말해서, 리버스 프록시는 클라이언트와 서버 사이에서 중개자 역할을 하는 서버입니다. 마치 호텔 프론트 데스크가 고객의 요청을 받아 적절한 부서로 연결해주는 것처럼, Nginx는 사용자 요청을 받아 백엔드 애플리케이션으로 전달하고 응답을 다시 사용자에게 돌려줍니다.
이 개념이 필요한 이유는 보안, 성능, 유연성 때문입니다. Nginx는 정적 파일(이미지, CSS, JS)을 매우 빠르게 서빙할 수 있어 Node.js가 동적 컨텐츠에만 집중하게 합니다.
또한 SSL 인증서를 Nginx에서 처리하면 애플리케이션 코드를 수정할 필요가 없습니다. 예를 들어, api.example.com은 백엔드로, app.example.com은 프론트엔드로, admin.example.com은 관리자 페이지로 연결하는 복잡한 라우팅을 쉽게 구현할 수 있습니다.
전통적인 방법에서는 각 애플리케이션이 직접 80번 포트를 사용하려고 하면 충돌이 발생했다면, 이제는 Nginx가 80번과 443번 포트를 관리하고 내부적으로 각 애플리케이션에 연결합니다. Nginx 리버스 프록시의 핵심 특징은 첫째, 단일 진입점입니다.
모든 트래픽이 Nginx를 거쳐가므로 로깅, 보안 정책, 속도 제한을 한 곳에서 관리할 수 있습니다. 둘째, 성능 최적화입니다.
캐싱, gzip 압축, 정적 파일 서빙을 Nginx가 담당하여 애플리케이션 부하를 줄입니다. 셋째, 무중단 배포입니다.
백엔드를 재시작해도 Nginx는 계속 실행되어 사용자에게 에러 페이지를 보여줄 수 있습니다. 이러한 특징들이 Nginx를 프로덕션 환경의 표준으로 만들었습니다.
코드 예제
# /etc/nginx/sites-available/myapp
server {
# 80번 포트에서 HTTP 요청 수신
listen 80;
server_name example.com www.example.com;
# 로그 파일 위치
access_log /var/log/nginx/myapp.access.log;
error_log /var/log/nginx/myapp.error.log;
# /api 경로는 백엔드 API로 전달
location /api {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# 정적 파일은 직접 서빙
location /static {
alias /var/www/myapp/public;
expires 1y;
add_header Cache-Control "public, immutable";
}
# 나머지 모든 요청은 프론트엔드로
location / {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
설명
이것이 하는 일: 위 Nginx 설정은 example.com으로 들어오는 모든 요청을 받아 경로에 따라 다른 백엔드 서버로 라우팅합니다. /api로 시작하는 요청은 3000번 포트의 백엔드 API로, /static은 로컬 파일 시스템에서 직접 서빙하고, 나머지는 3001번 포트의 프론트엔드로 전달합니다.
첫 번째로, server 블록은 하나의 가상 호스트를 정의합니다. listen 80은 HTTP 기본 포트에서 요청을 받고, server_name으로 이 설정이 처리할 도메인을 지정합니다.
여러 도메인을 공백으로 구분하여 나열할 수 있으며, www가 있는 것과 없는 것 모두 처리하려면 둘 다 적어야 합니다. access_log와 error_log는 트래픽 분석과 디버깅에 필수적입니다.
그 다음으로, location 블록들이 요청을 처리합니다. location /api는 가장 중요한 부분으로, proxy_pass로 실제 백엔드 서버 주소를 지정합니다.
proxy_set_header들은 원본 요청 정보를 백엔드에 전달하는데, 특히 X-Real-IP와 X-Forwarded-For는 실제 클라이언트 IP를 알기 위해 필수입니다. 프록시 뒤에서는 모든 요청이 localhost에서 오는 것처럼 보이기 때문입니다.
Upgrade와 Connection 헤더는 WebSocket 연결을 지원하기 위한 설정입니다. 마지막으로, location /static은 정적 파일을 Nginx가 직접 서빙합니다.
alias로 실제 파일 경로를 지정하고, expires와 Cache-Control로 브라우저 캐싱을 활성화합니다. 1년간 캐시하도록 설정하면 같은 파일을 반복 다운로드하지 않아 대역폭이 절약되고 로딩 속도가 빨라집니다.
정적 파일 서빙은 Nginx가 Node.js보다 10배 이상 빠르므로, 가능하면 Nginx가 직접 처리하게 하는 것이 좋습니다. 여러분이 이 Nginx 설정을 사용하면 복잡한 마이크로서비스 아키텍처도 하나의 도메인으로 통합할 수 있습니다.
예를 들어, /api는 Python Flask로, /admin은 Node.js Express로, 메인 페이지는 React로 만들어도 사용자는 하나의 사이트처럼 느낍니다. 또한 Nginx의 강력한 캐싱 기능을 활용하면 동일한 API 요청을 백엔드에 전달하지 않고 Nginx에서 바로 응답할 수 있어 데이터베이스 부하를 줄이고 응답 속도를 개선할 수 있습니다.
실전 팁
💡 설정 파일을 수정한 후 nginx -t로 문법 오류를 확인하고, sudo systemctl reload nginx로 재시작 없이 설정을 적용하세요.
💡 proxy_buffering on과 proxy_buffer_size를 조정하면 큰 응답을 효율적으로 처리할 수 있지만, 실시간 스트리밍에는 off로 설정해야 합니다.
💡 limit_req_zone을 사용하면 DDoS 공격을 방어할 수 있습니다. 예: 동일 IP에서 초당 10개 이상 요청을 차단.
💡 upstream 블록으로 여러 백엔드 서버를 정의하면 자동으로 로드 밸런싱되어 무중단 배포와 확장이 가능합니다.
💡 gzip on과 gzip_types를 설정하면 응답을 압축하여 전송 크기를 70% 이상 줄일 수 있어 모바일 사용자에게 특히 유리합니다.
6. SSL 인증서 설정
시작하며
여러분이 배포한 웹사이트에 사용자가 접속할 때 브라우저 주소창에 "주의 요함" 경고가 뜨거나, HTTPS가 아닌 HTTP로 접속되어 "안전하지 않음"이라는 표시가 나타나는 것을 본 적이 있나요? 현대 웹에서 HTTPS는 선택이 아닌 필수입니다.
구글은 HTTP 사이트의 검색 순위를 낮추고, 최신 브라우저들은 HTTP 사이트에서 많은 기능을 제한합니다. 이런 문제는 보안뿐만 아니라 사용자 신뢰와 SEO에도 직접적인 영향을 미칩니다.
사용자는 "안전하지 않음" 경고를 보면 사이트를 떠나고, 카메라나 위치 정보 같은 민감한 기능은 HTTPS 없이는 아예 작동하지 않습니다. 과거에는 SSL 인증서가 비쌌지만, 이제는 무료로 발급받을 수 있습니다.
바로 이럴 때 필요한 것이 Let's Encrypt SSL 인증서입니다. 무료로 자동 갱신되는 SSL 인증서를 몇 분 만에 발급받아 여러분의 사이트를 HTTPS로 전환할 수 있습니다.
개요
간단히 말해서, SSL/TLS 인증서는 서버와 클라이언트 간의 통신을 암호화하는 디지털 인증서입니다. 마치 은행에서 현금을 운반할 때 보안 차량을 사용하는 것처럼, 민감한 데이터를 전송할 때 SSL이 암호화하여 중간에서 누가 가로채도 읽을 수 없게 만듭니다.
이 개념이 필요한 이유는 사용자 데이터 보호와 신뢰 구축입니다. 로그인 정보, 신용카드 번호, 개인정보 등이 평문으로 전송되면 같은 Wi-Fi를 사용하는 해커가 쉽게 가로챌 수 있습니다.
HTTPS는 이런 데이터를 암호화하여 보호합니다. 예를 들어, 공공 와이파이에서 쇼핑몰에 로그인할 때 HTTPS가 없다면 비밀번호가 그대로 노출됩니다.
Let's Encrypt는 이런 보안을 무료로 제공하는 비영리 인증 기관입니다. 전통적인 방법에서는 인증서를 구매하고 수동으로 설치하며 매년 갱신했다면, 이제는 Certbot이라는 도구로 자동으로 발급받고 3개월마다 자동 갱신됩니다.
SSL 인증서의 핵심 특징은 첫째, 데이터 암호화입니다. 중간자 공격으로부터 데이터를 보호합니다.
둘째, 신원 확인입니다. 인증서는 이 사이트가 정말 example.com이라는 것을 증명합니다(피싱 방지).
셋째, 데이터 무결성입니다. 전송 중에 데이터가 변조되지 않았음을 보장합니다.
이러한 특징들이 HTTPS를 현대 웹의 기본 표준으로 만들었습니다.
코드 예제
#!/bin/bash
# Certbot으로 Let's Encrypt SSL 인증서 발급 및 설치
# Certbot 설치 (Ubuntu 기준)
sudo apt-get update
sudo apt-get install -y certbot python3-certbot-nginx
# Nginx를 사용하는 경우 자동으로 설정까지 완료
# example.com을 여러분의 도메인으로 변경하세요
sudo certbot --nginx -d example.com -d www.example.com \
--non-interactive --agree-tos --email admin@example.com
# 자동 갱신 테스트 (실제로 갱신하지는 않음)
sudo certbot renew --dry-run
# 자동 갱신을 위한 cron job 확인 (certbot이 자동으로 설정함)
sudo systemctl status certbot.timer
# Nginx 설정 다시 로드
sudo systemctl reload nginx
설명
이것이 하는 일: 위 스크립트는 Certbot을 설치하고 Let's Encrypt에서 SSL 인증서를 자동으로 발급받아 Nginx에 적용합니다. 도메인 소유권을 자동으로 인증하고, Nginx 설정 파일을 수정하여 HTTPS를 활성화하며, 3개월마다 자동으로 갱신되도록 설정합니다.
첫 번째로, certbot과 python3-certbot-nginx 패키지를 설치합니다. certbot은 인증서 발급 도구이고, nginx 플러그인은 Nginx 설정을 자동으로 수정해주는 역할을 합니다.
이 플러그인이 없으면 인증서는 발급받지만 수동으로 Nginx 설정을 수정해야 합니다. 플러그인을 사용하면 모든 과정이 자동화됩니다.
그 다음으로, certbot --nginx 명령어를 실행합니다. -d 옵션으로 도메인을 지정하는데, 여러 도메인을 하나의 인증서로 묶을 수 있습니다(www가 있는 것과 없는 것 모두 포함).
--non-interactive는 질문 없이 자동으로 진행하고, --agree-tos는 이용 약관에 동의하며, --email은 인증서 만료 알림을 받을 이메일입니다. Certbot은 도메인 소유권을 확인하기 위해 HTTP-01 챌린지를 수행하는데, 이는 특정 파일을 웹서버에 임시로 생성하여 Let's Encrypt가 확인하는 방식입니다.
마지막으로, certbot renew --dry-run으로 자동 갱신이 제대로 작동하는지 테스트합니다. --dry-run은 실제로 갱신하지 않고 시뮬레이션만 합니다.
Let's Encrypt 인증서는 90일마다 만료되는데, Certbot은 자동으로 systemd 타이머를 설정하여 매일 2번 갱신을 시도합니다. 만료 30일 전부터 갱신이 가능하므로 실제로는 60일마다 자동 갱신됩니다.
여러분이 이 설정을 완료하면 브라우저 주소창에 자물쇠 아이콘이 표시되고, HTTP로 접속해도 자동으로 HTTPS로 리다이렉트됩니다. Certbot이 Nginx 설정에 자동으로 추가하는 내용은 SSL 인증서 경로, 강력한 암호화 설정(TLS 1.2/1.3), HTTP를 HTTPS로 리다이렉트하는 규칙 등입니다.
또한 HSTS(HTTP Strict Transport Security) 헤더를 추가하면 브라우저가 항상 HTTPS로만 접속하도록 강제할 수 있어 보안이 더욱 강화됩니다.
실전 팁
💡 Certbot 실행 전에 도메인의 A 레코드가 서버 IP를 가리키고 있어야 합니다. 그렇지 않으면 도메인 소유권 확인이 실패합니다.
💡 여러 서브도메인이 있다면 *.example.com 와일드카드 인증서를 발급받을 수 있지만, DNS-01 챌린지가 필요하므로 DNS API 접근 권한이 있어야 합니다.
💡 인증서 갱신 실패 알림을 받으면 즉시 확인하세요. 만료되면 사이트 접속이 차단됩니다. certbot renew --force-renewal로 수동 갱신할 수 있습니다.
💡 Nginx 설정에 ssl_protocols TLSv1.2 TLSv1.3; 를 추가하여 취약한 구버전 프로토콜을 비활성화하면 보안이 강화됩니다.
💡 SSL Labs(ssllabs.com/ssltest)에서 여러분의 사이트를 테스트하면 SSL 설정의 보안 등급(A+가 최고)을 확인할 수 있습니다.
7. GitHub Actions CI/CD 파이프라인
시작하며
여러분이 코드를 수정하고 Git에 푸시한 후, SSH로 서버에 접속하고, Git pull을 실행하고, Docker 이미지를 다시 빌드하고, 컨테이너를 재시작하는 일련의 과정을 매번 반복하고 계신가요? 이 과정에서 한 단계라도 빠뜨리거나 잘못 실행하면 서비스가 중단될 수 있습니다.
이런 문제는 수동 배포의 본질적인 한계입니다. 사람은 실수하기 마련이고, 특히 늦은 밤 긴급 버그 수정을 배포할 때는 더욱 그렇습니다.
또한 팀원이 여러 명이라면 각자 다른 방식으로 배포할 수 있어 일관성이 없고, 누가 언제 무엇을 배포했는지 추적하기 어렵습니다. 바로 이럴 때 필요한 것이 GitHub Actions CI/CD 파이프라인입니다.
코드를 푸시하면 자동으로 테스트하고, 빌드하고, 배포까지 완료되는 완전 자동화된 워크플로우를 구축할 수 있습니다.
개요
간단히 말해서, CI/CD는 Continuous Integration(지속적 통합)과 Continuous Deployment(지속적 배포)의 약자로, 코드 변경사항을 자동으로 테스트하고 배포하는 프로세스입니다. GitHub Actions는 GitHub 저장소에 내장된 CI/CD 도구로, YAML 파일 하나로 복잡한 자동화 워크플로우를 정의할 수 있습니다.
이 개념이 필요한 이유는 배포 자동화와 품질 보장입니다. 모든 코드 변경이 자동으로 테스트되므로 버그가 프로덕션에 배포되기 전에 발견됩니다.
또한 배포 프로세스가 표준화되어 누가 배포하든 동일한 결과가 보장됩니다. 예를 들어, 풀 리퀘스트를 올리면 자동으로 테스트가 실행되고 코드 리뷰어가 결과를 확인한 후 머지하면 자동으로 프로덕션에 배포되는 워크플로우를 구축할 수 있습니다.
전통적인 방법에서는 "금요일에는 배포하지 말자"는 불문율이 있었다면, 이제는 자동화된 테스트와 배포로 언제든 안심하고 배포할 수 있습니다. GitHub Actions의 핵심 특징은 첫째, GitHub 통합입니다.
저장소에서 모든 것이 관리되고, 별도의 CI/CD 서비스 가입이 필요 없습니다. 둘째, 유연성입니다.
수천 개의 미리 만들어진 액션을 조합하거나 직접 만들 수 있습니다. 셋째, 무료 사용량입니다.
공개 저장소는 무제한, 비공개 저장소는 월 2,000분 무료입니다. 이러한 특징들이 GitHub Actions를 현대적인 개발 워크플로우의 중심으로 만들었습니다.
코드 예제
# .github/workflows/deploy.yml
name: Deploy to EC2
on:
push:
branches: [ main ] # main 브랜치에 푸시될 때 실행
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# 저장소 코드 체크아웃
- uses: actions/checkout@v3
# Node.js 환경 설정
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
# 의존성 설치 및 테스트 실행
- name: Install and Test
run: |
npm ci
npm run test
# Docker 이미지 빌드
- name: Build Docker Image
run: docker build -t myapp:${{ github.sha }} .
# EC2에 배포
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /home/ubuntu/myapp
git pull origin main
docker-compose down
docker-compose up -d --build
설명
이것이 하는 일: 위 워크플로우는 main 브랜치에 코드가 푸시되면 자동으로 실행됩니다. Node.js 환경을 설정하고, 의존성을 설치하고, 테스트를 실행하고, Docker 이미지를 빌드한 후, EC2 서버에 SSH로 접속하여 애플리케이션을 배포합니다.
첫 번째로, on 섹션에서 워크플로우 트리거를 정의합니다. push 이벤트의 main 브랜치로 제한하여 개발 브랜치 푸시는 배포되지 않도록 합니다.
pull_request를 추가하면 PR이 올라올 때마다 테스트를 실행할 수 있고, schedule로 정기적인 빌드도 가능합니다. jobs 섹션에서 deploy라는 작업을 정의하고, runs-on으로 Ubuntu 최신 버전에서 실행하도록 지정합니다.
그 다음으로, steps가 순차적으로 실행됩니다. actions/checkout@v3는 GitHub에서 제공하는 공식 액션으로 저장소 코드를 다운로드합니다.
actions/setup-node@v3는 Node.js 18을 설치합니다. 여기서 중요한 것은 각 step이 독립적인 환경에서 실행되므로 이전 step의 상태를 유지하려면 캐싱이나 아티팩트를 사용해야 한다는 점입니다.
npm ci는 package-lock.json을 기반으로 정확한 버전을 설치하여 재현성을 보장합니다. 마지막으로, SSH 액션으로 EC2에 접속하여 배포합니다.
${{ secrets.EC2_HOST }}는 GitHub 저장소 Settings > Secrets에 저장된 암호화된 값입니다. 절대 코드에 서버 정보나 SSH 키를 직접 적으면 안 됩니다.
script 섹션의 명령어들이 EC2에서 실행되며, git pull로 최신 코드를 받고 docker-compose로 재배포합니다. --build 옵션은 이미지를 다시 빌드하여 코드 변경사항이 반영되도록 합니다.
여러분이 이 워크플로우를 설정하면 코드 푸시 후 2-3분 안에 자동으로 배포가 완료됩니다. GitHub Actions 탭에서 실행 이력과 로그를 확인할 수 있어 배포 실패 시 빠르게 원인을 파악할 수 있습니다.
또한 Slack이나 Discord로 배포 성공/실패 알림을 보내도록 설정하면 팀 전체가 배포 상태를 실시간으로 파악할 수 있습니다. 더 나아가 카나리 배포나 블루-그린 배포 같은 고급 배포 전략도 워크플로우에 추가할 수 있습니다.
실전 팁
💡 테스트가 실패하면 배포가 중단되도록 하려면 각 step 뒤에 if: success()를 추가하거나, 테스트 step이 실패하면 자동으로 중단됩니다.
💡 Secrets에 저장한 값은 로그에 출력되지 않도록 자동으로 마스킹되지만, echo로 직접 출력하면 노출될 수 있으니 주의하세요.
💡 actions/cache를 사용하여 node_modules를 캐싱하면 빌드 시간을 50% 이상 단축할 수 있습니다.
💡 프로덕션 배포 전에 스테이징 환경에 먼저 배포하는 multi-stage 워크플로우를 구성하면 더 안전합니다.
💡 GitHub Actions는 분 단위로 과금되므로, 불필요한 워크플로우 실행을 줄이려면 paths 필터로 특정 파일 변경 시에만 실행되도록 설정하세요.
댓글 (0)
함께 보면 좋은 카드 뉴스
보안 강화 및 테스트 완벽 가이드
웹 애플리케이션의 보안 취약점을 방어하고 안정적인 서비스를 제공하기 위한 실전 보안 기법과 테스트 전략을 다룹니다. XSS, CSRF부터 DDoS 방어, Rate Limiting까지 실무에서 바로 적용 가능한 보안 솔루션을 제공합니다.
Redis 캐싱과 Socket.io 클러스터링 완벽 가이드
실시간 채팅 서비스의 성능을 획기적으로 향상시키는 Redis 캐싱 전략과 Socket.io 클러스터링 방법을 배워봅니다. 다중 서버 환경에서도 안정적으로 작동하는 실시간 애플리케이션을 구축하는 방법을 단계별로 알아봅니다.
반응형 디자인 및 UX 최적화 완벽 가이드
모바일부터 데스크톱까지 완벽하게 대응하는 반응형 웹 디자인과 사용자 경험을 개선하는 실전 기법을 학습합니다. Tailwind CSS를 활용한 빠른 개발부터 다크모드, 무한 스크롤, 스켈레톤 로딩까지 최신 UX 패턴을 실무에 바로 적용할 수 있습니다.
React 채팅 UI 구현 완벽 가이드
실시간 채팅 애플리케이션의 UI를 React로 구현하는 방법을 다룹니다. Socket.io 연동부터 컴포넌트 설계, 상태 관리까지 실무에 바로 적용할 수 있는 내용을 담았습니다.
실시간 알림 및 푸시 시스템 완벽 가이드
웹과 모바일 앱에서 사용자에게 실시간으로 알림을 전달하는 방법을 배워봅니다. 새 메시지 알림부터 FCM 푸시 서버 구축까지, 실무에서 바로 사용할 수 있는 알림 시스템 구현 방법을 단계별로 알아봅니다.