iBetter Books
수정

Docker로 배포하기

"내 컴퓨터에서는 잘 되는데..."라는 말은 배포 현장에서 자주 나옵니다. Docker는 앱과 모든 의존성을 컨테이너 안에 담아서 어디서든 같은 환경으로 실행합니다. rocker 프로젝트가 제공하는 rocker/shiny 이미지 덕분에 Shiny 앱 컨테이너화는 비교적 간단합니다.

rocker/shiny 이미지

Rocker 프로젝트는 R 공식 Docker 이미지를 관리합니다. Shiny 앱 배포에는 rocker/shiny 이미지를 씁니다.

# 이미지 내려받기 (선택 사항 — Dockerfile 빌드 시 자동 다운로드)docker pull rocker/shiny:4.3.0

Dockerfile 작성

modular-app/
├── app.R
├── global.R
├── R/
│   └── filter_module.R
├── data/
│   └── large_dataset.parquet
└── Dockerfile        ← 새로 작성
# 새 파일: modular-app/DockerfileFROM rocker/shiny:4.3.0# 시스템 의존성 설치RUN apt-get update && apt-get install -y \    libcurl4-openssl-dev \    libssl-dev \    libxml2-dev \    libfontconfig1-dev \    && rm -rf /var/lib/apt/lists/*# R 패키지 설치RUN R -e "install.packages(c( \    'bslib', \    'tidyverse', \    'plotly', \    'DT', \    'arrow', \    'memoise', \    'rmarkdown' \  ), repos='https://cloud.r-project.org/')"# 앱 파일 복사COPY . /srv/shiny-server/modular-app/# 권한 설정RUN chown -R shiny:shiny /srv/shiny-server/modular-app# 포트 노출EXPOSE 3838# Shiny Server 실행 (rocker/shiny 기본 CMD)CMD ["/usr/bin/shiny-server"]

이미지 빌드

cd modular-appdocker build -t my-shiny-app:latest .

처음 빌드는 패키지 설치 때문에 10~20분 걸립니다. 이후에는 캐시 덕분에 코드 변경 시 1~2분으로 줄어듭니다.

컨테이너 실행

docker run -d \  --name shiny-app \  -p 3838:3838 \  my-shiny-app:latest

브라우저에서 http://localhost:3838/modular-app에 접속합니다.

# 컨테이너 상태 확인docker ps# 로그 확인docker logs shiny-app -f# 컨테이너 중지docker stop shiny-app# 컨테이너 삭제docker rm shiny-app

docker-compose로 서비스 관리

여러 컨테이너(Shiny + Nginx + 데이터베이스)를 함께 관리할 때는 docker-compose가 편합니다.

# 새 파일: docker-compose.ymlversion: "3.8"services:  shiny:    build:      context: ./modular-app      dockerfile: Dockerfile    container_name: shiny-server    restart: unless-stopped    ports:      - "3838:3838"    volumes:      - ./data:/srv/shiny-server/modular-app/data   # 데이터 마운트    environment:      - TZ=Asia/Seoul  nginx:    image: nginx:alpine    container_name: nginx-proxy    restart: unless-stopped    ports:      - "80:80"      - "443:443"    volumes:      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf      - ./ssl:/etc/nginx/ssl    depends_on:      - shiny
# 서비스 시작docker compose up -d# 서비스 중지docker compose down# 로그 확인docker compose logs -f shiny

환경 변수 관리

API 키나 데이터베이스 비밀번호를 코드에 직접 넣으면 안 됩니다. .env 파일로 분리합니다.

# 새 파일: .env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=shinydb
DB_PASSWORD=mysecret
API_KEY=abc123
# docker-compose.ymlservices:  shiny:    env_file: .env    environment:      - DB_HOST=${DB_HOST}      - DB_PASSWORD=${DB_PASSWORD}

Shiny 앱에서는 Sys.getenv()로 읽습니다.

# app.R 또는 global.R
db_password <- Sys.getenv("DB_PASSWORD")
api_key     <- Sys.getenv("API_KEY")

.env 파일은 반드시 .gitignore에 추가해서 Git에 올라가지 않도록 합니다.

echo ".env" >> .gitignore

빌드 시간 단축 팁

패키지 설치가 가장 느립니다. renv로 패키지 목록을 고정하면 재빌드 시 캐시를 활용할 수 있습니다.

# 개발 환경에서
install.packages("renv")
renv::init()       # renv.lock 파일 생성
renv::snapshot()   # 현재 패키지 상태 기록
# Dockerfile에서 renv 활용FROM rocker/shiny:4.3.0RUN R -e "install.packages('renv', repos='https://cloud.r-project.org/')"WORKDIR /srv/shiny-server/modular-app# renv.lock만 먼저 복사 (패키지 캐시 레이어 분리)COPY renv.lock .RUN R -e "renv::restore()"# 나머지 파일 복사COPY . .

renv.lock이 바뀌지 않으면 패키지 레이어가 캐시되어 코드 변경 시 빌드가 빠릅니다.