Skip to main content

Command Palette

Search for a command to run...

Docker image & Container 빌드, 관리

Updated
6 min read
Docker image & Container 빌드, 관리

1. 이미지 & 컨테이너

1.1. 이미지 vs 컨테이너

1.1.1. 이미지

도커에서 서비스 운영에 필요한 서버, 소스코드, 라이브러리 등 필요한 것들을 묶어서 가지고 있는 상태를 Docker image라고 한다. 그런데 이 이미지는 설정값과 파일들을 가지고 있지만 실행중이라는 것을 의미하지는 않는다.

1.1.2. 컨테이너

그 이미지를 실제로 실행하고 있는 것은 컨테이너라고 한다. 단순히 실행 안 하면 이미지, 실행하면 컨테이너라기보단 이미지가 어떠한 틀이라면 그걸로 컨테이너를 찍어낼(실행) 할 수 있는 것이다. 그래서 이미지 하나로 여러 컨테이너를 생성하는 것도 가능하고, 컨테이너를 삭제해도 이미지는 변하지 않는다.


1.2. Dockerfile

아까 설명한 이미지를 만들기 위해 작성하는 파일이다. 예시로

FROM node

WORKDIR /app

COPY . /app

RUN npm install

EXPOSE 80

CMD ["node", "server.js"]

이렇게 생긴 도커파일이 있을 수 있다. 위 도커파일을 살펴보면 처음에 FROM이 있는데,

1.2.1. FROM

FROM은 도커파일에서 베이스 이미지를 지정하는 지시이다. 도커 이미지를 만들 때 무의 상태에서 만들어가는 게 아니라 어떤 베이스를 가지고 그 위에 쌓는 게 일반적인데, 이런 베이스를 지정하는 것이다. 위의 코드에서는 node를 베이스로 하는 이미지를 만들 것이라는 뜻이다.

1.2.2. WORKDIR

컨테이너 내부의 작업 디렉토리를 /app 으로 설정한다는 의미이다. 이후의 모든 명령은 /app에서 실행되게 된다.

1.2.3. COPY

호스트 머신의 현재 디렉토리를 컨테이너의 /app디렉토리로 복사하는 것이다. 로컬에서 작업한 코드들이 이 과정을 통해 컨테이너로 들어가게 된다.

1.2.4. RUN

컨테이너 내부에서 실행할 명령이다. 위 도커파일에서는 npm install 명령을 실행해서 컨테이너 내부에 의존성들을 설치하게 된다.

1.2.5. EXPOSE 80

컨테이너가 80번 포트를 사용함을 명시한다. 그런데 이것은 실제로 기능하는 것이 아니라, 문서화 목적으로 쓰이는 것이다. 그래서 이 명령어를 쓴다고 실제 포트 매핑이 되는 것은 아니다. 실제 포트 매핑은 도커 실행 시 -p 옵션을 써서 진행해야한다.

1.2.6. CMD

컨테이너가 시작될 때 실행될 기본 명령이다. RUN은 빌드 시점에 실행되지만, CMD는 컨테이너 실행 시 작동한다. 그래서 RUN의 결과는 이미지에 포함되게 되고, 설치와 설정 작업에 이용된다. CMD는 컨테이너가 실행될 때 어떤 프로세스를 시작할지 지정하는 명령어로, CMD 여러 개를 써도 마지막에 지정한 프로세스가 시작된다.


1.3. Dockerfile로 빌드하기

도커파일이 준비되면 docker build 명령을 사용하여 이미지를 빌드할 수 있다.

docker build .

도커파일이 있는 위치에서 위 커맨드를 실행하면 이미지가 빌드된다.

빌드된 이미지를 바탕으로 컨테이너 실행은 docker run으로 진행할 수 있다. 빌드된 이미지는 읽기 전용이기 때문에 변경사항이 생기면 이미지를 다시 빌드하고 돌려야한다.

아까 말했듯 EXPOSE80 자체는 문서화 목적이라 실제 포트 매핑이 되지 않는다. 그래서 옵션 없이 실행하면 localhost로 빌드한 웹 앱에 접속할 수 없다.

docker run -p 3000:80

1.4. 이미지 레이어

도커 이미지는 레이어들로 구성되는데, 이 구조가 있어서 도커가 매번 명령을 처음부터 실행할 필요가 없다. 도커는 매번 명령을 캐싱해두었다가 이미지를 다시 빌드할 때 명령을 다시 실행할 필요가 없으면(변경사항이 없으면) 캐싱된 결과를 다시 가져와서 사용한다.

따라서 그 원리를 이용하면

FROM node

WORKDIR /app

COPY . /app

RUN npm install

EXPOSE 80

CMD ["node", "server.js"]

도커파일에서

FROM node

WORKDIR /app

COPY package.json /app

RUN npm install

COPY . /app

EXPOSE 80

CMD ["node", "server.js"]

으로 바꾸게 되면 npm install이 재실행되는 것을 막을 수 있다는 것이다.


2. 이미지 & 컨테이너 관리

2.1. 컨테이너 생성, 실행, 중지

docker [command] --help

도커 명령어들에 --help을 달게 되면 사용법이 출력된다. run명령어와 start명령어는 다르다. run은 컨테이너를 생성하고 실행하지만, start는 기존에 존재하는 컨테이너를 시작하는 것이다. 따라서 run에는 이미지명이 들어가야하고 start는 컨테이너명이 들어간다. 컨테이너를 중지하려면 stop명령어를 사용하면 된다.

docker run [옵션] [이미지명]
docker start [컨테이너명]
docker stop [컨테이너명]

2.2. Attached & Detached

한국어로는 포그라운드 & 백그라운드로 번역하는 것 같다. attach detach 개념은 실행 여부와는 별개의 것인데, attached일 때 현재 실행하는 터미널에 컨테이너의 동작 상태를 출력하게 된다. detached라면 실행은 하고 있지만, 현재 터미널에 출력되고 있지는 않다.

docker run명령어 실행 시 디폴트로 attached, docker start 시 디폴트로 detached가 된다. 디폴트를 따라야 하는 것은 아니고

docker run -d [이름]
docker start -a [이름]

와 같이 옵션을 붙여주면 디폴트로 attached가 되어야하지만 detached로 실행된다.

실제 실행해보면

  ~/devops/study_docker/nodeapp ─────────────────────────────────────────────────────────────────────────────────────────────────  max-env at  21:22:49 ─╮
❯ docker run -p 3000:80 -d a1bab505873c                                                                                                                     ─╯
6be1ec3c5c838336604d90598df4c2798ae4a6e65e5232812a59e30d7b5a33e1

이런 식으로 나온다.

만약 다시 터미널에서 동작 상태를 보고 싶다면, 두 가지 방법이 있는데

2.2.1. 다시 Attach

docker attach [이름]

으로 다시 포그라운드에서 돌게 할 수 있다.

2.2.2. Log 가져오기

또 다른 방법으로는 컨테이너에 출력되는 로그를 log 명령어로 가지고 올 수 있다.

docker logs [이름]

으로 attached상태에서 출력되는 것들이 나오게 된다.


2.3. 인터렉티브 모드로 들어가기

localhost로 접속할 수 있는 형태가 아니라 터미널에서 상호작용하는 프로그램을 처리하려면 어떻게 해야하는가?

from random import randint

min_number = int(input("Please enter the min number: "))
max_number = int(input("Please enter the max number: "))

if (max_number < min_number):
    print("Invalid input - shutting down..")
else:
    rnd_number = randint(min_number, max_number)
    print(rnd_number)

만약에 이런 코드를

FROM python

WORKDIR /app

COPY . /app

CMD ["python", "rng.py"]

로 돜파일을 작성해 이미지를 빌드했을 떄, docker run을 그냥 실행해버리면 에러만 출력된다. 그 이유는 현재 입력을 터미널 외부에서 줄 수가 없는 상황이기 때문이다. detached로 바꿔도 입력을 줄 수 없는 것은 마찬가지라 터미널에서 상호작용이 가능하도록 하는 다른 옵션이 필요하다.

도움이 될 수 있는 옵션은 -i-t인데 각각 인터렉티브 모드와 터미널 생성을 의미한다. 이 옵션은 같이 쓰는 것도 가능하다.

docker run -it 023043bd53c8                                                                                                                               ─╯
Please enter the min number: 1
Please enter the max number: 1
1
docker start -a -i trusting_easley

로도 인터랙티브 모드를 쓸 수 있다.


2.4. 이미지 & 컨테이너 삭제하기

docker rm [컨테이너명]

으로 컨테이너를 삭제할 수 있지만 실행중인 컨테이너는 삭제할 수 없기 때문에 중지해야 한다.

이미지 삭제는

docker rmi [이미지]

를 입력하면 레이어를 비롯해서 이미지가 삭제된다. 그런데 이미지를 쓰고있는 컨테이너가 존재하는 경우 이미지를 삭제할 수 없기 때문에 컨테이너를 먼저 제거해야한다.

컨테이너가 중지되면 자동으로 삭제되게 할 수 있는데

docker run --rm [컨테이너명]

으로 중지될 때마다 자동 삭제되게 할 수도 있다. 노드 서버와 같은 것들은 코드 변경 같은 상황이 아니면 중지하지 않는 경우가 많은데, 이런 것들은 어차피 다시 빌드해야하기 때문에 자동삭제 옵션은 꽤 유용한 편이다.


2.5. 이미지 검사

docker image inspect [이미지]

로 이미지에 대한 정보를 확인할 수 있다. 엔트리 포인트, 도커 버전, 사용중인 운영체제에 대한 정보들도 포함한다. 레이어에 대한 정보들도 여기서 확인할 수 있는데, 도커 파일의 명령어 개수보다 많을 수 있다. 이것들은 베이스에 있는 것들이 포함된 것으로 볼 수 있다.


2.6. 컨테이너 파일 복사하기

호스트에 있는 파일을 컨테이너로 복사하려면

docker cp [복사할 파일 경로] [컨테이너명]:[컨테이너 내부 파일 경로]

로 복사할 수 있다.

반대로 컨테이너에서 호스트로 복사하려면

docker cp [컨테이너명]:[컨테이너 내부 파일 경로] [복사할 파일 경로]

로 쓰면 된다.


2.7. 컨테이너와 이미지 이름&태그

컨테이너를 만들면 아무 의미 없는 이름이 붙는다. 원하는 이름을 붙여주기 위해서는

docker run --name [원하는 이름] [이미지]

--name옵션을 이용하여 이름을 붙여줄 수 있다.

이름 변경 시에는

docker rename [과거 이름] [변경할 이름]

으로 바꿀 수 있다.

이미지도 마찬가지로 그냥 생성하면 숫자로 구성된 ID만 나오게 된다. 이미지는 이름:태그 로 구성할 수 있다.

docker build -t imagename:ver1 .

와 같이 -t옵션을 이용하면 된다.

이미지 이름, 태그 변경은

docker image tag [과거 이름:태그] [새로운이름:태그]

로 변경할 수 있다.


3. 이미지 공유하기

3.1. Dockerhub에 이미지 push하기

도커 이미지는 개인 레포에도 올릴 수 있고, 도커허브에도 push 가능하다. 도커에는 이미지 공유를 위해 내장된 명령어가 있다.

Dockerhub에 가입하고 웹에서 리포지토리를 생성하고 여기에 이미지를 푸시하려면 docker push명령어를 사용하면 되는데, 이때 Dockerhub에서 생성한 리포지토리 이름과 로컬 이미지 이름이 같아야한다. 유저네임까지 포함해서 username/dockerhubrepo의 형식이어야 한다.

해당 이름의 이미지를 다시 빌드해도 되지만 이미지 이름을 변경해서 푸시해도 된다.

로그인을 안 하고 push하려고 하면 permission denied가 뜨는데, docker login으로 로그인해주면 된다.

3.2. 공유 이미지 가져오기

반대로 남이 Dockerhub에 올린 이미지도 가져올 수 있다. 명령어는

docker pull username/repo

로 로컬에 이미지를 가져올 수 있다.

18 views

More from this blog

락프리 데이터 구조와 알고리즘

여기서는 락프리 데이터 구조를 설명한다. 락프리(lock-free) 란 배타락을 이용하지 않고 처리를 수행하는 데이터 구조 및 그에 대한 조작 알고리즘을 총칭한다. 왜 락프리인가? 전통적인 동시성 제어 방법인 뮤텍스나 세마포어는 여러 문제점을 가지고 있다: 성능 저하: 락 경합(lock contention)으로 인한 대기 시간 데드락: 여러 스레드가 서로의 락을 기다리는 상황 우선순위 역전: 낮은 우선순위 스레드가 높은 우선순위 스레드를 ...

Jul 27, 20257 min read119

소프트웨어 트랜잭셔널 메모리

소프트웨어 트랜잭셔널 메모리 동시성 프로그래밍에서 공유 자원에 대한 안전한 접근은 항상 중요한 과제다. 전통적으로 뮤텍스 락과 같은 비관적 락(Negative Lock) 방식을 사용해왔다. 이 방식은 크리티컬 섹션에 진입하기 전에 반드시 락을 획득해야 하며, 락을 얻지 못하면 코드 실행 자체가 블록된다. 하지만 이와는 다른 접근 방식이 있다. 바로 낙관적 락(Optimistic Lock) 방식인데, 이는 "일단 실행하고 나중에 검증하자"는 철학...

Jul 20, 202517 min read261

공평한 배타 제어

공평한 배타 제어 여기서는 공평한 배타 제어에 대해 설명한다. 먼저 컨텐션(contention) 이라는 개념을 이해할 필요가 있다. 컨텐션이란 여러 스레드가 동시에 같은 락을 획득하려고 경쟁하는 상황을 말한다. 컨텐션이 높을수록 스레드들이 락을 기다리는 시간이 길어지고 성능이 저하된다. 이러한 컨텐션 상황은 시스템 아키텍처에 따라 더욱 복잡해질 수 있다. 특히 비균일 메모리 접근(Non-Uniform Memory Access, NUMA) 와 같...

Jul 13, 20259 min read21

KernelSnitch[논문 리뷰]

Paper 1. Intro 이 글은 NDSS 2025에서 발표된 KernelSnitch 논문을 소개이다. 이 연구는 커널의 평범한 데이터 구조체들이 가진 본질적인 특성이 어떻게 심각한 보안 취약점이 되는지를 보여준다. 핵심은 이러하다: "데이터 구조체의 크기에 따른 접근 시간 차이를 이용해 커널의 비밀 정보를 유출할 수 있다" 여기서는 커널 힙 포인터 유출에 집중해서 설명한다. 이 공격이 성공하면 KASLR을 우회하고 더 심각한 커널 익스플로...

Jul 11, 20257 min read124

멀티태스크와 액터 모델

멀티태스크 협조적/비협조적 멀티태스크 선점: 프로세스와의 협조 없이 수행하는 컨택스트 스위칭이라고는 하나, 결국 뺏어오는 게 가능하냐의 문제다. 협조적 멀티태스크(비선점형, cooperative): 각각의 프로세스가 자발적으로 컨택스트 스위칭을 수행하는 멀티태스크 방식. 장점: 멀티태스크 매커니즘을 구현하기 쉽다. 단점: 프로세스가 자발적으로 컨텍스트 스위칭을 해야하는데, 만약 버그가 발생하여 프로세스가 무한 루프에 빠지거나 정지하게 되면 그 ...

Jul 6, 20252 min read24
M

MaxLog

35 posts

Docker image & Container 빌드, 관리