Intro
이번 포스팅에선 도커 이미지를 만들어 보겠다.
1. 도커 이미지 만들기
도커는 이미지를 만들기 위해 컨테이너의 상태를 그대로 이미지로 저장하는 방법을 사용한다.

예를 들어, 어떤 애플리케이션을 이미지로 만든다면 리눅스만 설치된 컨테이너에 애플리케이션을 설치하고, 그 상태를 그대로 이미지로 저장한다. (like 가상머신 스냅샷)
이런 과정은 콘솔에서 명령어를 직접 입력하는 것과 별 차이가 없으므로 쉘 스크립트를 잘 알아야 하지만,
좋은 샘플이 많이 공개되어 있어 잘 몰라도 된다.
그리고 컨테이너의 가벼운 특성과 레이어 개념을 이용하여 생성과 테스트를 빠르게 수행할 수 있다.
1) Sinatra 웹 애플리케이션 샘플
Riuby로 만들어진 간단한 웹 애플리케이션을 도커라이징(Dockerizing) 해보자.
Ruby: 동적 오픈 소스 프로그래밍 언어(단순함, 생산성)
도커라이징(Dockerizing): 도커 이미지를 만듦
1. 도커로 ruby 설치
sinatra 웹 프레임워크를 사용하기 위해 새로운 폴더(ruby)를 만들고,
Gemfile과 app.rb를 생성한다.
Gemfile: 패키지 관리
source 'https://rubygems.org'
gem 'sinatra'
app.rb: 호스트명을 출력하는 웹 서버 생성
require 'sinatra'
require 'socket'
get '/' do
Socket.gethostname
end
패키지를 설치하고 서버를 실행해보자.
bundle install # install package
bundle exec ruby app.rb # Run sinatra
여기서 필자는 아래 오류가 발생해서 추가 작업을 진행하였다.
root@DESKTOP-DOPLEQK:~# bundle install
-bash: /mnt/c/Ruby32-x64/bin/bundle: ruby: bad interpreter: Permission denied
#Ruby 설치
sudo apt update
sudo apt install ruby-full
#Bundler 재설치
gem install bundler
#번들 설치
cd ruby
bundle install
그리고 sinatra를 실행하려면 rackup gem이 필요하다. 다음 단계를 차근차근 수행해보자.
#rackup gem 설치
gem install rackup
#Gemfile에 rackup 추가 >>Gemfile을 수정해야 함<<
gem 'rackup'
#번들 설치
bundle install
#서버 실행
bundle exec ruby app.rb
개인적으로 서버가 실행될 때까지 시도했던 명령어를 쭉 나열한 거라서 더 최적화 된 순서가 있을 수도 있다.!

우여곡절이 있었지만 Ruby를 설치하고 서버 실행에 성공하였다.
정말 서버 실행에 성공했는지 확인해보자. (http://localhost:4567)

도커 컨테이너의 호스트명이 출력된 걸 볼 수 있다. 이제 도커 이미지를 만들 준비가 된 것이다.
호스트의 디렉토리를 ruby가 설치된 컨테이너의 디렉토리에 마운트한 후,
그대로 명령어를 실행하면 로컬에 개발 환경을 구축하지 않고 도커 컨테이너를 개발 환경으로 사용할 수 있다.
2) Ruby Application Dockerfile

도커는 이미지를 만들기 위해 Dockerfile 이라는 이미지 빌드용 DSL 파일을 사용한다.
이는 단순 텍스트 파일로, 일반적으로 소스와 함께 관리한다.
먼저 Ruby 웹 애플리케이션을 ubuntu에 배포하는 과정을 살펴보겠다.
- ubuntu 설치
- ruby 설치
- 소스 복사
- Gem 패키지 설치
- Sinatra 서버 실행
↓ 쉘 스크립트
# 1. ubuntu 설치 (패키지 업데이트)
apt-get update
# 2. ruby 설치
apt-get install ruby
gem install bundler
# 3. 소스 복사
mkdir -p /usr/src/app
scp Gemfile app.rb root@ubuntu:/usr/src/app # From host
# 4. Gem 패키지 설치
bundle install
# 5. Sinatra 서버 실행
bundle exec ruby app.rb
ubuntu 컨테이너를 실행하고 위 명령어를 그대로 실행하면 웹 서버를 실행할 수 있다.
리눅스에서 테스트가 끝났으니 이 과정을 Dockerfile로 만들면 된다.
핵심 명령어는 파일을 복사하는 COPY와 명령어를 실행하는 RUN이다.
이 쉘 스크립트의 내용을 그대로 Dockerfile로 옮겨보자.
# 1. ubuntu 설치 (패키지 업데이트 + 만든사람 표시)
FROM ubuntu:22.04
MAINTAINER subicura@subicura.com
RUN apt-get -y update # -y 옵션 추가(질의 방지)
# 2. ruby 설치
RUN apt-get -y install ruby
RUN gem install bundler
# 3. 소스 복사
COPY . /usr/src/app
# 4. Gem 패키지 설치 (실행 디렉토리 설정)
WORKDIR /usr/src/app
RUN bundle install
# 5. Sinatra 서버 실행 (Listen 포트 정의)
EXPOSE 4567
CMD bundle exec ruby app.rb -o 0.0.0.0
도커 빌드 중에는 키보드를 입력할 수 없기 때문에 (y/n) 질의를 방지하기 위해 -y 옵션을 추가했다.
3) Docker build
빌드 파일을 작성했으니 이제 이미지를 만들어보자.
이미지 빌드 명령어
docker build [OPTIONS] PATH | URL | -
생성할 이미지 이름을 지정하기 위한 -t(--tag) 옵션만 알면 빌드가 가능하다.
Dockerfile을 만든 디렉토리로 이동하여 다음 명령어를 입력하자.
docker build -t app .

로그를 보면 Dockerfile에 정의한 내용이 한 줄 한 줄 실행되는 걸 볼 수 있다.
실제로 명령어를 실행하기 때문에 빌드 시간이 꽤 걸린다.
이제 이미지가 잘 생성되었는지 확인해 보자. [성공]

잘 동작하는지도 확인하기 위해 컨테이너를 실행해보자!


신기해서 나도 서버를 3개나 만들어 버림;; 의미 없어요
아무튼 성공했다.
4) Dockerfile 기본 명령어
이미지를 만드는 데 사용하는 Dockerfile의 기본적인 명령어를 살펴보자.
모든 명령어를 보고 싶으면 공식 문서를 참고하자.
FROM
FROM <image>:<tag>
FROM ubuntu:22.04
베이스 이미지를 지정하는 명령어이다.
반드시 지정해야 하며, 어떤 이미지도 베이스 이미지가 될 수 있다.
<tag>는 latest(기본 값)보다 구체적인 버전(22.04)을 지정하는 것이 좋다.
참고: 베이스 이미지
MAINTAINER
MAINTAINER <name>
MAINTAINER subicura@subicura.com
Dockerfile을 관리하는 사람의 이름 또는 이메일 정보를 적는다. (빌드에 영향X)
COPY
COPY <src>... <dest>
COPY . /usr/src/app
파일이나 디렉토리를 이미지로 복사한다.
일반적으로 소스를 복사하는 데 사용한다.
target 디렉토리가 없다면 자동으로 생성한다.
ADD
ADD <src>... <dest>
ADD . /usr/src/app
COPY 명령어와 유사하지만 추가 기능이 있다.
src에 파일 대신 url을 입력할 수 있고, src에 압축 파일을 입력하는 경우 자동으로 압축을 해제하면서 복사된다(!!)
RUN
RUN <command>
RUN ["executable", "param1", "param2"]
RUN bundle install
가장 많이 사용하는 구문으로, 명령어를 그대로 실행한다.
내부적으로 /bin/sh -c 뒤에 명령어를 실행하는 방식이다.
CMD
CMD ["executable","param1","param2"]
CMD command param1 param2
CMD bundle exec ruby app.rb
도커 컨테이너가 실행되었을 때 실행되는 명령어를 정의한다.
빌드할 때는 실행되지 않고, 여러 개의 CMD가 존재할 때 가장 마지막 CMD만 실행된다.
한꺼번에 여러 개의 프로그램을 실행하고 싶은 경우, run.sh 파일을 작성하여 데몬으로 실행하거나
supervisord나 forego와 같은 여러 개의 프로그램을 실행하는 프로그램을 사용한다.
supervisord: 프로세스 제어 시스템. 사용자가 UNIX와 유사한 운영체제에서 여러 프로세스를 모니터링하고 제어할 수 있도록 하는 클라이언트/서버 시스템
forego도 비슷한 맥락인 듯..?
WORKDIR
WORKDIR /path/to/workdir
RUN, CMD, ADD, COPY 등이 이루어질 기본 디렉토리를 설정한다.
각 명령어의 현재 디렉토리는 한 줄 한 줄마다 초기화되기 때문에 RUN cd /path 를 하더라도 다음 명령어에서는 다시 위치가 초기화된다.
그러므로 같은 디렉토리에서 계속 작업하기 위해 사용한다.
EXPOSE
EXPOSE <port> [<port>...]
EXPOSE 4567
도커 컨테이너가 실행됐을 때 요청을 기다리고 있는 포트를 지정한다.
여러 개의 포트를 지정할 수 있다.
VOLUME
VOLUME ["/data"]
컨테이너 외부에 파일 시스템을 마운트 할 때 사용한다.
반드시 지정하지 않아도 마운트할 수 있지만, 기본적으로 지정하는 것이 좋다.
ENV
ENV <key> <value>
ENV <key>=<value> ...
ENV DB_URL mysql
컨테이너에서 환경변수를 지정한다.
컨테이너를 실행할 때 -e 옵션을 사용하면 기존 값을 오버라이딩 하게 된다.
5) Build 분석
도커는 Dockerfile을 가지고 무슨 일을 하는 걸까?
build 로그를 살펴 보자.
Sending build context to Docker daemon 5.12 kB <-- (1)
Step 1/10 : FROM ubuntu:22.04 <-- (2)
---> f49eec89601e <-- (3)
Step 2/10 : MAINTAINER subicura@subicura.com <-- (4)
---> Running in f4de0c750abb <-- (5)
---> 4a400609ff73 <-- (6)
Removing intermediate container f4de0c750abb <-- (7)
Step 3/10 : RUN apt-get -y update <-- (8)
...
...
Successfully built 20369cef9829 <-- (9)
(1) Sending build context to Docker daemon 5.12 kB
빌드 명령어를 실행한 디렉토리의 파일들을 빌드 컨텍스트(Build Context)라고 한다.
이 빌드 컨텍스트를 도커 서버(Daemon)으로 전송하는 작업을 진행한 것이다.
도커는 서버-클라이언트 구조이므로, 도커 서버가 작업하려면 미리 파일을 전송해야 한다.
(2) Step 1/10 : FROM ubuntu:22.04
Dockerfile을 한 줄 한 줄 수행하는데, 첫 번째로 FROM 명령어를 수행한다.
ubuntu:22.04 이미지를 다운받는 작업을 진행한다.
(3) ---> f49eec89601e # ubuntu 이미지 ID
명령어 수행 결과를 이미지로 저장한다.
(4) Step 2/10 : MAINTAINER subicura@subicura.com
Dockerfile의 두 번째 명령어인 MAINTAINER를 수행한다.
(5) ---> Running in f4de0c750abb
명령어를 수행하기 위해 바로 이전에 생성된 f49eec89601e 이미지를 기반으로 f4de0c750abb 컨테이너를 임시로 생성하여 실행한다.
(6) ---> 4a400609ff73
명령어 수행 결과를 이미지로 저장한다.
(7) Removing intermediate container f4de0c750abb
명령어를 수행하기 위해 임시로 만들었던 컨테이너를 제거한다.
(8) Step 3/10 : RUN apt-get -y update
Dockerfile의 세 번째 명령어인 RUN을 수행한다.
이전에 생성된 이미지를 기반으로 임시 컨테이너를 생성하여 명령어를 실행하고, 그 결과 상태를 이미지로 만든다.
이 과정을 마지막 줄까지 반복한다.
(9) Successfully built 20369cef9829
최종 성공한 이미지 ID를 출력한다.
도커 빌드 과정
- 임시 컨테이너 생성
- 명령어 수행
- 이미지로 저장
- 임시 컨테이너 삭제
위 과정을 반복한다고 볼 수 있다.
명령어를 실행할 때마다 이미지 레이어를 저장하고, 다시 빌드할 때 Dockerfile이 변경되지 않았다면 기존에 저장된 이미지를 그대로 캐시처럼 사용한다.
6) 이미지 리팩토링
앞에서 만든 이미지는 몇 가지 최적화 문제가 존재한다.
Base Image
위에서 만든 Ruby 애플리케이션 이미지는 ubuntu를 베이스로 만들었지만, 사실 훨씬 간단한 ruby 베이스 이미지가 존재한다.
기존에 ruby를 설치했던 명령어는 ruby 이미지를 사용하는 것으로 간단하게 생략할 수 있다.
# Dockerfile
# before
FROM ubuntu:22.04
MAINTAINER subicura@subicura.com
RUN apt-get -y update
RUN apt-get -y install ruby
RUN gem install bundler
# after
FROM ruby:2.3
MAINTAINER subicura@subicura.com
ruby 이외에도 node.js, python, java, go 등 다양한 베이스 이미지가 존재한다.
Build Cache
기존에 빌드했던 이미지를 다시 빌드해보자.

빌드 시간이 대폭 감소한 걸 확인할 수 있다. [23.3s] -> [5.4s]
이미지를 빌드하는 과정에서 각 단계를 이미지 레이어로 저장하고, 다음 빌드에서 캐시로 사용한다.
도커는 빌드할 때 Dockerfile의 명령어가 수정되거나 추가되는 파일이 변경되었을 때, 캐시가 깨지고 그 이후 작업은 새로 이미지를 만들게 된다.
ruby gem 패키지를 설치하는 과정은 꽤 많은 시간이 소요되는데 최대한 캐시를 이용하여 빌드 시간을 줄여야 한다.
기존 소스에서 소스 파일이 수정되면 캐시가 깨지는 부분은 아래와 같다.
COPY . /usr/src/app # <- 소스파일이 변경되면 캐시가 깨짐
WORKDIR /usr/src/app
RUN bundle install # 패키지를 추가하지 않았는데 또 인스톨하게 됨
복사하는 파일이 이전과 다르면 캐시를 사용하지 않고 그 이후 명령어는 다시 실행된다.
ruby gem 패키지를 관리하는 파일은 Gemfile이고, Gemfile은 잘 수정되지 않으므로 다음과 같이 순서를 바꿀 수 있다.
COPY Gemfile* /usr/src/app/ # Gemfile을 먼저 복사함
WORKDIR /usr/src/app
RUN bundle install # 패키지 인스톨
COPY . /usr/src/app # <- 소스가 바꼈을 때 캐시가 깨지는 시점
명령어 최적화
# before
RUN apt-get -y update
# after
RUN apt-get -y -qq update
-qq: 불필요한 로그를 무시할 수 있게 했다.
# before
RUN bundle install
# after
RUN bundle install --no-rdoc --no-ri
--no-doc, --no-ri: 필요 없는 문서를 생성하지 않아 이미지 용량도 줄이고, 빌드 속도도 더 빠르게 했다.
명령어 배치
# before
RUN apt-get -y -qq update
RUN apt-get -y -qq install ruby
# after
RUN apt-get -y -qq update && \
apt-get -y -qq install ruby
비슷한 명령어 끼리 묶어 레이어 수를 줄일 수 있도록 하였다.
결과
FROM ruby:2.3
MAINTAINER subicura@subicura.com
COPY Gemfile* /usr/src/app/
WORKDIR /usr/src/app
RUN bundle install --no-rdoc --no-ri
COPY . /usr/src/app
EXPOSE 4567
CMD bundle exec ruby app.rb -o 0.0.0.0
이로써 이미지가 깔끔하게 만들어졌다.
참고: https://subicura.com/2017/02/10/docker-guide-for-beginners-create-image-and-deploy.html
'log.info' 카테고리의 다른 글
[Docker] 데이터 관리 및 볼륨 (0) | 2024.06.14 |
---|---|
[Docker] 이미지, 컨테이너 요약 (0) | 2024.06.13 |
[Docker] 컨테이너 업데이트, Docker Compose (0) | 2024.05.28 |
[Docker] 컨테이너 실행 및 도커 기본 명령어 (0) | 2024.05.28 |
[Docker] 설치 (0) | 2024.05.28 |