해당 글에서는 CI/CD 파이프라인이 구축되기 전에 사용자의 수동 배포를 최소한으로 줄이기 위해 임시적으로 사용하는 SFTP 방식을 이용한 사례에 대해서 작성한 글입니다.
1) 배포방식
💡 배포방식
- 현재 배포 환경은 CI/CD 파이프라인이 구축되기 이전 단계로 GitHub Actions Runner 같은 자동화 도구를 통한 배포가 아닌, 사용자가 수동 배포 과정을 최소한으로 줄이기 위해 임시로 운영하는 방식입니다.
- 별도의 Docker Registry를 운영하지 않기 때문에, 개발 PC에서 Docker 이미지를 빌드한 뒤 docker save 명령으로 .tar 파일로 저장합니다. 이 파일을 SFTP를 통해 운영 서버로 전송하고, 운영 서버에서 docker load로 이미지를 로드한 뒤 docker compose up -d로 서비스를 실행하는 흐름입니다.
- 이 방식은 Registry 구축 없이도 이미지를 전달할 수 있고, 인터넷이 차단된 폐쇄망 환경에서도 적용 가능하다는 장점이 있습니다. 반면 매 배포마다 수동 작업이 필요하고, 배포 이력 관리가 어려우며, 이미지 태그 불일치 등의 휴먼 에러가 발생할 수 있다는 한계가 있습니다.
- 향후 CI/CD가 구축되면, git push를 트리거로 Runner가 자동으로 이미지를 빌드하고 Registry에 push 한 뒤 운영 서버에서 pull 하여 배포하는 구조로 전환될 예정이며, 현재의 수동 과정은 그 과도기에 사용하는 임시 배포 방식입니다.
1. 배포 처리 과정
💡 배포 처리 과정
1. 소스코드 - 배포의 시작점으로 React, Spring Boot 등 로컬에서 개발 완료된 상태의 코드를 준비합니다. - 또한, .env, docker-compose.yml, Dockerfile 등이 함께 준비되어 있어야 합니다.
2. Docker 이미지 빌드 - Dockerfile을 기반으로 소스코드를 컨테이너 이미지로 패키징 하는 단계를 의미합니다. - 이 시점에 OS, 런타임, 의존성, 앱 코드가 하나의 이미지로 묶이며, 빌드 결과물은 로컬 Docker에만 존재해야 합니다.
3. tar 파일 Export - Docker 이미지를 파일로 직렬화하는 단계를 의미합니다. Docker Hub 같은 외부 레지스트리 없이 이미지를 전송하기 위해 사용이 됩니다.
4. SFTP 전송 - PC에서 압축된 tar 파일을 배포될 원격 서버로 복사를 하는 과정입니다. 이 과정에서는 FTP 또는 SFTP 전송을 하며, SFTP 클라이언트(FileZila, Termius 등)를 사용하여 전송을 합니다.
5. 파일 수신 (서버 폴더 이동) - 서버에서 전송받은 파일 위치를 확인하는 단계이며, 별도 작업이라기보다 다음 단계 전 준비 확인 과정입니다.
6. 이미지 로드 - 서버의 Docker에 이미지를 등록하는 단계를 의미합니다.
7. 컨테이너 실행 - compose.yml 기반으로 컨테이너를 백그라운드로 실행. 포트, 볼륨, 환경변수 등이 이 파일에 정의되어 있는 파일을 실행합니다.
8. 배포 완료 - 서비스 정상 동작 확인합니다.
2. 서버 내 compose.yaml
💡 서버 내 compose.yaml
- 해당 서버는 배포될 리눅스 서버를 의미하며, 이를 위치하며 Docker Compose를 통하여서 배포가 이루어집니다.
💡 depends_on 기반 서비스 실행 과정
- 해당 서비스는 db → api → web 순서로 기동 됩니다.
1) db : PostgreSQL
- postgres:17.9 이미지로 컨테이너를 생성하며 기본적인 TimeZone 설정을 지정하였습니다.
1. command, environment 속성: 기본적인 TimeZone 설정을 지정하였습니다. 2. env_file 속성: 서버 내에 위치해 있는 파일로, 기본적인 db 연결을 위한 POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD를 지정하였습니다.
3. volumes 속성 : pgdata:/var/lib/postgresql/data 컨테이너를 삭제하더라도 데이터를 유지하기 위해 지정하였습니다.
2) api : Spring Boot
- 사전에 외부에서 .tar 파일로 빌드된 이미지를 가져와, 이미지 로드로 불러온 Spring Boot 컨테이너 이미지를 사용합니다.
1. env_file 속성: 서버 내에 위치해 있는 파일로, DB 접속 정보, Spring 프로파일 등 환경변수 주입을 하는 속성입니다.
2. volumes 속성 : bind mount로 호스트의 ./uploads 디렉토리를 컨테이너 /uploads에 매핑되며, 파일 업로드 데이터가 호스트에 직접 저장됩니다.
3. environment 속성: JVM/OS 레벨 타임존 설정을 합니다.
4. restart 속성: docker stop을 하기 전까지 수행이 됩니다.
3) web : React - 사전에 외부에서 .tar 파일로 빌드된 이미지를 가져와, 이미지 로드로 불러온 Vite로 빌드된 정적 파일을 서빙하는 Nginx 컨테이너 이미지를 사용합니다. 1. volumes 속성: volumes로 호스트의 default.conf를 Nginx 설정에 bind mount → 이미지 재빌드 없이 Nginx 설정 변경 가능하도록 구성하였습니다. (컨테이너 재시작만 하면 됨)
- Gradle 빌드 → DockerFile 이미지 빌드 → tar 파일로 export까지 수행을 하는 과정입니다.
# shell 파일 실행
$ ./build-and-export.sh
💡 [참고] Docker Desktop이 실행이 되었는지 확인을 합니다. 아래와 같이 오류가 발생함.
- Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.4.1/userguide/configuration_cache_enabling.html ERROR: Cannot connect to the Docker daemon at unix:///Users/leejonghoon/.docker/run/docker.sock. Is the docker daemon running? Cannot connect to the Docker daemon at unix:///Users/leejonghoon/.docker/run/docker.sock. Is the docker daemon running?
- Docker Desktop을 실행시키면 됩니다.
4. 아래와 같이 tar 파일이 생성되었습니다.
5. [Termius] 연결된 리눅스 서버에 SFTP로 해당 파일을 옮겨둡니다
6. [Termius] tar 파일을 로드해 옵니다.
$ docker load -i xxx-api.tar
7. [Termius] Docker 컨테이너 배포 완료
💡 [Termius] Docker 컨테이너 배포 완료
- Docker 컨테이너를 재 실행하여 정상적으로 소스코드 반영이 완료되었습니다.
docker compose up -d
3) 화면 배포(React)
1. DockerFile
💡 DockerFile 구성
- 루트 경로에 소스코드를 이미지화 시키기 위한 Dockerfile을 구성합니다.
💡 Dockerfile 파일 설명 1. FROM node:24.15.0-alpine - Node.js 24.15.0 기반의 Alpine Linux 이미지 사용하며, Alpine은 경량 Linux 배포판으로 이미지 크기를 최소화하였습니다 2. WORKDIR /app - 컨테이너 안에서 작업 디렉토리를 /app으로 설정합니다.
3. RUN yarn global add serve - 정적 파일 서버인 serve 패키지를 전역 설치하며, React 빌드 결과물(정적 HTML/JS/CSS)을 HTTP로 서빙하는 역할을 수행합니다. 4. COPY dist/ ./dist/ - 로컬 머신의 dist/ 폴더(빌드 결과물)를 컨테이너의 /app/dist/로 복사합니다. 5. EXPOSE 80 - 컨테이너가 80번 포트를 사용한다고 선언합니다