메뉴 로딩...

빅데이터 전문가 양성과정

도커

AWS EC2 서버 생성·접속부터 도커 컨테이너 안에서 사용하는 리눅스 기본 명령어와 VIM 편집기 사용법까지 정리했습니다.

한눈에 보기

  1. 1. EC2 서버 생성 — 리전 서울, 키 페어, 30GB, 탄력적 IP
  2. 2. 내 PC에서 접속 — chmod 400 + ssh / PuTTY(.ppk)
  3. 3. 셸 기본 명령어 — whoami, pwd, ls
  4. 4. 컨테이너 접속 — docker run / docker exec
  5. 5. 파일 다루기 — cat, rm, cp
  6. 6. 프로세스 확인·종료 — ps aux | grep, kill
  7. 7. 패키지 관리 — apt-get update / upgrade / install / remove
  8. 8. 파일 편집 — vim (i → Esc → :wq)
  9. 9. 도커 설치 — Windows / Ubuntu, docker-compose
  10. 10. Docker Internals — 컨테이너와 가상 머신의 차이
  11. 11. 이미지와 컨테이너 — 개념 비교와 nginx 실습 예제
  12. 12. 이미지 검색·다운로드와 컨테이너 상태 — search, pull, rmi
  13. 13. 컨테이너 실행 명령어 — docker run 옵션과 start 차이
  14. 14. Dockerfile 작성 — FROM, COPY, RUN, CMD와 build
  15. 15. docker-compose 기본 사용법 — yml 작성과 up/down

1. EC2 서버 만들기

EC2는 AWS에서 빌려 쓰는 가상 서버입니다. 인스턴스를 만든 뒤 탄력적 IP를 연결하는 순서로 진행합니다.

  1. 리전(Region)을 서울로 설정

    AWS 콘솔 오른쪽 위에서 리전을 서울(ap-northeast-2)로 변경한 뒤 EC2 인스턴스를 만듭니다. 리전이 다르면 만든 서버가 목록에 보이지 않습니다.

  2. 키 페어(Key pair) 생성·저장

    새 키 페어를 만들고 .pem 파일을 내려받아 안전한 곳에 보관합니다. 이 파일이 있어야 서버에 접속할 수 있고, 분실하면 다시 받을 수 없습니다.

    Windows에서 PuTTY로 접속한다면 .ppk 형식으로 받거나 PuTTYgen으로 변환합니다.

  3. 스토리지 30GB 설정

    스토리지 구성에서 볼륨 크기를 30GB로 지정합니다. 프리 티어는 30GB까지 무료입니다.

  4. 탄력적 IP(Elastic IP) 연결

    인스턴스를 껐다 켜면 공인 IP가 바뀝니다. 탄력적 IP를 할당한 뒤 인스턴스에 연결하면 항상 같은 주소로 접속할 수 있습니다.

    탄력적 IP 주소 할당 → 작업 → 탄력적 IP 주소 연결 → 인스턴스 선택

2. 내 PC에서 EC2 접속하기

내려받은 키 파일과 탄력적 IP로 접속합니다. Ubuntu 이미지의 기본 사용자 이름은 ubuntu입니다.

macOS · Linux

키 파일 권한을 400(소유자 읽기 전용)으로 바꾼 뒤 ssh로 접속합니다. 권한이 열려 있으면 접속이 거부됩니다.

chmod 400 my-key-pair.pem
ssh -i my-key-pair.pem ubuntu@탄력적IP

Windows (PuTTY)

PuTTYgen으로 .pem을 .ppk로 변환한 뒤, PuTTY에서 키 파일을 지정하고 접속합니다.

1. PuTTYgen 실행 → my-key-pair.pem 불러오기 → my-key-pair.ppk 저장
2. PuTTY → Connection → SSH → Auth → Credentials → .ppk 파일 선택
3. Session → Host Name: ubuntu@탄력적IP → Open

3. 셸(shell)과 기본 명령어

은 사용자가 입력한 명령을 컴퓨터 하드웨어(운영체제) 에 전달하는 인터페이스입니다. 리눅스의 대표 셸은 bash입니다.

  • whoami지금 로그인한 사용자 이름 확인
    whoami
  • pwd현재 위치한 디렉터리 경로 출력
    pwd
  • ls현재 디렉터리의 파일·폴더 목록 출력
    ls
    ls -a
    ls -l
    ls -al
  • sudo apt-get update패키지 목록 갱신 (서버 접속 후 가장 먼저 실행)
    sudo apt-get update

ls 옵션

  • -a숨김 파일(.으로 시작하는 파일)까지 모두 표시
  • -l권한·소유자·크기·수정일 등 상세 정보를 한 줄씩 표시
  • -al숨김 파일 + 상세 정보를 함께 표시

권한 표기 읽는 법

ls -l을 실행하면 맨 앞에 rwxrwxr-- 같은 권한이 표시됩니다. 세 글자씩 끊어서 읽습니다. (r 읽기 · w 쓰기 · x 실행)

  • 소유자

    rwx

    읽기 · 쓰기 · 실행 모두 가능

  • 그룹

    rwx

    같은 그룹 사용자도 모두 가능

  • 기타

    r--

    그 외 사용자는 읽기만 가능

접속용 키 파일에 사용하는 chmod 400은 소유자에게 읽기 권한만 주고 나머지는 모두 막는다는 뜻입니다.

4. 컨테이너 접속하기

아래 docker 명령어는 도커가 설치된 서버(또는 내 PC) 셸에서 실행합니다. 접속 이후 5~8번의 리눅스 명령어는 컨테이너 내부 셸에서 실행합니다.

  1. Ubuntu 컨테이너 실행

    Ubuntu 이미지를 내려받아 컨테이너를 만들고 바로 셸(bash)에 접속합니다. --name으로 컨테이너 이름을 지정해 두면 다시 접속하기 편합니다.

    docker run -it --name ubuntu-lab ubuntu:22.04 /bin/bash
  2. 실행 중인 컨테이너 확인

    현재 실행 중인 컨테이너 목록을 확인합니다. 멈춘 컨테이너까지 보려면 -a 옵션을 붙입니다.

    docker ps
    docker ps -a
  3. 컨테이너에 다시 접속

    이미 실행 중인 컨테이너 내부 셸로 들어갑니다. 멈춘 상태라면 docker start로 먼저 켭니다.

    docker start ubuntu-lab
    docker exec -it ubuntu-lab /bin/bash
  4. 컨테이너 빠져나오기

    exit를 입력하면 셸에서 나옵니다. 컨테이너를 끄지 않고 나오려면 Ctrl + P, Ctrl + Q를 사용합니다.

    exit

5. 파일 다루기 — cat, rm, cp

  • cat파일 내용 읽기
    cat test.txt
  • rm파일·폴더 삭제
    rm test.txt
    rm -r 폴더명
    rm -rf 폴더명
    • -r하위 디렉터리까지 함께 삭제 (폴더 삭제 시 필수)
    • -f확인 없이 강제로 삭제
  • cp파일·폴더 복사 (cp 원본 대상)
    cp test.txt ~/test
    cp -r 폴더명 ~/backup
    • -r폴더를 하위 내용까지 통째로 복사
    • -f대상 파일이 이미 있으면 덮어쓰기
    • -i덮어쓰기 전에 한 번 물어보기

6. 프로세스 확인·종료 — ps, kill

ps aux 결과의 두 번째 열이 PID(프로세스 번호)입니다. 이 번호로 프로세스를 종료합니다.

  • ps aux실행 중인 모든 프로세스 상태 확인
    ps aux
  • ps aux | grep특정 이름의 프로세스만 골라서 확인
    ps aux | grep bash
  • killPID(프로세스 번호)로 프로세스 종료
    kill 1234
    kill -9 1234

kill은 정상 종료 요청, kill -9는 강제 종료입니다. 강제 종료는 저장되지 않은 작업이 사라질 수 있으니 먼저 일반 kill을 사용하세요.

7. 패키지 관리 — apt-get

Ubuntu에서 프로그램을 설치·삭제하는 명령어입니다. 설치 전에는 항상 update를 먼저 실행합니다.

명령어설명
sudo apt-get update패키지 목록(인덱스) 정보를 갱신합니다. 설치된 프로그램 자체가 최신이 되는 것은 아닙니다.
sudo apt-get upgrade설치된 패키지를 최신 버전으로 업그레이드합니다.
sudo apt-get install 패키지명새 패키지를 설치합니다. 예: sudo apt-get install vim
sudo apt-get remove 패키지명설치된 패키지를 삭제합니다.
도커 Ubuntu 이미지는 기본 사용자가 root이고 sudo가 설치되어 있지 않을 수 있습니다. 이때는 sudo를 빼고 apt-get update처럼 실행하면 됩니다.

8. VIM 사용법

VIM은 명령 모드입력 모드가 나뉜 편집기입니다. 글자를 입력하려면 먼저 입력 모드로 들어가야 합니다.

  1. vim 파일명
  2. i (입력 모드)
  3. 내용 편집
  4. Esc (명령 모드)
  5. :wq (저장 후 종료)
입력사용 위치동작
vim 파일명파일을 열거나 새로 만들어 편집 시작
i명령 모드입력(insert) 모드로 전환 — 글자 입력 가능
Esc입력 모드명령 모드로 돌아가기
x명령 모드커서 위치의 글자 한 개 삭제
:wq명령 모드저장하고 종료 (write + quit)
:q!명령 모드저장하지 않고 강제로 나가기

실습 시 주의사항

  • 키 페어(.pem)는 재발급되지 않습니다. 분실하면 서버에 접속할 수 없으니 안전한 곳에 보관하세요.
  • 탄력적 IP를 연결하지 않으면 인스턴스를 재시작할 때마다 공인 IP가 바뀝니다.
  • 실습이 끝나면 인스턴스를 중지하고, 사용하지 않는 탄력적 IP는 해제하세요. 연결되지 않은 탄력적 IP에는 요금이 부과됩니다.
  • rm -rf는 확인 없이 지우고 복구할 수 없습니다. 경로를 반드시 확인한 뒤 실행하세요.
  • apt-get update는 패키지 목록만 갱신합니다. 실제 업그레이드는 upgrade까지 실행해야 합니다.
  • VIM이 종료되지 않으면 Esc를 먼저 누른 뒤 :q!를 입력하세요.
  • 컨테이너를 삭제하면 내부에서 만든 파일도 함께 사라집니다. 남겨야 할 결과물은 호스트로 복사하거나 볼륨을 사용하세요.
  • Git 기초는 깃이란? 페이지를 참고하세요.

9. 도커 설치

개인 PC(Windows)와 리눅스 서버(Ubuntu)는 설치 방법이 다릅니다. 설치 후에는 항상 docker -v로 버전을 확인합니다.

개인 PC (Windows)

  1. BIOS에서 가상화 기능 켜기

    부팅 시 BIOS에 진입해 Intel Virtualization Technology(AMD는 SVM Mode)를 활성화합니다. 이 옵션이 꺼져 있으면 도커가 실행되지 않습니다.

  2. Hyper-V 활성화

    Windows 기능 켜기/끄기에서 Hyper-V와 가상 머신 플랫폼을 체크한 뒤 재부팅합니다.

  3. Docker Desktop for Windows 설치

    공식 사이트에서 Docker Desktop for Windows를 내려받아 설치하고 실행합니다.

  4. 설치 확인

    터미널에서 버전이 출력되면 설치가 완료된 것입니다.

    docker -v

리눅스 서버 (Ubuntu)

  1. 공식 문서대로 설치

    docker install ubuntu로 검색해 공식 문서(docs.docker.com)의 설치 절차를 그대로 따릅니다.

    docker -v
  2. 현재 사용자를 docker 그룹에 추가

    docker 그룹에 넣어 두면 매번 sudo를 붙이지 않고 docker 명령을 쓸 수 있습니다. ${USER}는 현재 로그인한 사용자 이름으로 자동 치환됩니다.

    sudo usermod -aG docker ${USER}

    적용하려면 exit로 로그아웃한 뒤 다시 접속해야 합니다.

  3. 재접속으로도 안 되면 재부팅

    그룹 변경이 반영되지 않으면 서버를 재부팅한 뒤 다시 접속합니다.

    sudo systemctl reboot

docker-compose 설치

여러 컨테이너를 설정 파일 하나로 함께 실행하는 도구입니다. Ubuntu 서버에는 standalone 버전을 내려받아 실행 권한을 줍니다.

  1. standalone 바이너리 내려받기

    docker compose standalone install ubuntu로 검색해 공식 문서의 다운로드 명령을 실행합니다.

  2. 실행 권한 부여

    내려받은 파일에 실행 권한(x)을 줘야 명령어로 사용할 수 있습니다.

    sudo chmod +x /usr/local/bin/docker-compose
  3. 설치 확인

    버전이 출력되면 설치가 완료된 것입니다.

    docker-compose --version

10. Docker Internals

리눅스 컨테이너는 한 대의 서버 안에서 별도의 가상 컴퓨터처럼 동작하는 격리된 실행 환경입니다. 프로세스·파일 시스템· 네트워크가 서로 분리되어 있어, 같은 서버에서 여러 프로그램을 충돌 없이 실행할 수 있습니다.

구분가상 머신(VM)컨테이너
운영체제게스트 OS를 통째로 설치호스트의 리눅스 커널을 공유
실행 속도부팅에 수십 초~수 분프로세스 실행 수준으로 수 초
자원 사용량OS 단위로 크게 차지필요한 프로그램만큼만 사용

컨테이너는 호스트의 리눅스 커널을 공유하기 때문에 가상 머신보다 가볍고 빠르게 실행됩니다.

11. 도커 이미지와 컨테이너

도커를 쓸 때 가장 자주 헷갈리는 두 개념입니다. 한 문장으로 정리하면 이미지는 틀, 컨테이너는 그 틀로 찍어낸 결과물입니다.

이미지 (Image)

붕어빵 틀 · 프로그램 설치 파일

실행에 필요한 프로그램·설정·파일을 통째로 담아 둔 읽기 전용 템플릿입니다. 한 번 만들어지면 내용이 변하지 않습니다.

  • 읽기 전용이라 실행해도 내용이 바뀌지 않음
  • Docker Hub에서 내려받거나 Dockerfile로 직접 빌드
  • docker images 로 목록 확인

컨테이너 (Container)

붕어빵 · 실행 중인 프로그램

이미지를 실행해 만들어진 격리된 실행 환경입니다. 이미지 위에 쓰기 공간이 얹혀 있어 파일을 만들고 지울 수 있습니다.

  • 이미지 하나로 컨테이너 여러 개를 동시에 실행 가능
  • 중지·재시작·삭제할 수 있고 서로 영향을 주지 않음
  • docker ps -a 로 목록 확인

만들어지는 흐름

  1. Dockerfile

    설치 순서를 적은 파일

  2. docker build

    이미지로 굽기

  3. 이미지

    Docker Hub에서 pull 도 가능

  4. docker run

    이미지를 실행

  5. 컨테이너

    실제로 동작하는 환경

예제: nginx 웹서버 띄워보기

이미지를 받아 컨테이너로 실행하고 정리하는 전체 과정입니다. 명령어를 순서대로 따라 하면 됩니다.

  1. 이미지 내려받기

    Docker Hub에서 nginx 웹서버 이미지를 받아옵니다. 아직 실행된 것은 없습니다.

    docker pull nginx
  2. 받아온 이미지 확인

    내 서버에 저장된 이미지 목록을 봅니다.

    docker images
    
    REPOSITORY   TAG       IMAGE ID       SIZE
    nginx        latest    5ef79149e0ec   188MB
  3. 이미지를 실행해 컨테이너 만들기

    -d는 백그라운드 실행, -p는 포트 연결(내 서버 8080 → 컨테이너 80), --name은 컨테이너 이름입니다.

    docker run -d -p 8080:80 --name my-web nginx
  4. 실행 중인 컨테이너 확인

    STATUS가 Up이면 정상입니다. 브라우저에서 http://서버주소:8080 으로 접속하면 nginx 기본 화면이 보입니다.

    docker ps
    
    CONTAINER ID   IMAGE   STATUS         PORTS                  NAMES
    9f2c1b3a4d5e   nginx   Up 5 seconds   0.0.0.0:8080->80/tcp   my-web
  5. 컨테이너 안으로 들어가 보기

    컨테이너 내부는 독립된 리눅스 환경입니다. 앞에서 배운 ls, pwd 같은 명령어를 그대로 쓸 수 있습니다.

    docker exec -it my-web /bin/bash
    ls /usr/share/nginx/html
    exit
  6. 중지하고 다시 시작하기

    중지해도 컨테이너는 남아 있어서 언제든 다시 켤 수 있습니다. 내부에서 만든 파일도 그대로 유지됩니다.

    docker stop my-web
    docker start my-web
  7. 정리하기 (컨테이너 → 이미지 순서)

    컨테이너를 먼저 삭제해야 이미지를 지울 수 있습니다. 이미지를 지워도 다시 pull 하면 됩니다.

    docker rm -f my-web
    docker rmi nginx

이미지 1개 → 컨테이너 여러 개

같은 nginx 이미지로 컨테이너를 여러 개 만들 수 있습니다. 포트와 이름만 다르게 지정하면 됩니다.

docker run -d -p 8081:80 --name web1 nginx
docker run -d -p 8082:80 --name web2 nginx

web1 안에서 파일을 지워도 web2와 원본 이미지에는 아무 영향이 없습니다. 컨테이너끼리는 완전히 분리되어 있습니다.

명령어 비교

작업이미지컨테이너
목록 보기docker imagesdocker ps -a
가져오기 · 실행docker pull 이미지명docker run 이미지명
새로 만들기docker build -t 이미지명 .docker commit 컨테이너명 이미지명
삭제docker rmi 이미지명docker rm 컨테이너명
데이터는 컨테이너와 함께 사라집니다. 컨테이너를 삭제하면 그 안에서 만든 파일도 지워집니다. 계속 보관해야 할 데이터는 -v 옵션으로 서버 폴더와 연결해 두세요.
docker run -d -p 8080:80 -v ~/html:/usr/share/nginx/html --name my-web nginx

12. 이미지 검색·다운로드와 컨테이너 상태

이미지는 Docker Hub라는 저장소에서 내려받습니다. 검색 → 다운로드 → 확인 → 삭제 순서로 다루고, 그 이미지로 만든 컨테이너가 지금 어떤 상태인지 읽는 법까지 정리했습니다.

자주 쓰는 이미지

  • ubuntu

    리눅스 기본 환경

    명령어 실습이나 다른 프로그램을 직접 설치해 볼 때 쓰는 기본 리눅스 이미지입니다.

  • httpd (Apache)

    웹서버

    아파치 웹서버가 미리 설치된 이미지입니다. 실행하면 바로 웹서버가 동작합니다.

  • nginx

    웹서버

    가볍고 빠른 웹서버 이미지입니다. 정적 파일 서비스나 리버스 프록시에 많이 사용합니다.

이미지 다루기 명령어

  • docker loginDocker Hub 로그인 (비공개 이미지 받기·올리기에 필요)
    docker login
    docker logout

    공개(public) 이미지는 로그인 없이도 내려받을 수 있습니다.

  • docker searchDocker Hub에서 이미지 검색
    docker search ubuntu

    OFFICIAL 열에 [OK]가 있으면 공식 이미지입니다.

  • docker pull이미지 내려받기 (태그를 생략하면 latest)
    docker pull ubuntu
    docker pull ubuntu:22.04
  • docker images내려받은 이미지 목록 확인
    docker images
    docker image ls -q

    -q(quiet)는 IMAGE ID만 출력합니다. 여러 이미지를 한 번에 지울 때 유용합니다.

  • docker rmi내려받은 이미지 삭제
    docker rmi 이미지ID
    docker rmi $(docker image ls -q)

    해당 이미지로 만든 컨테이너가 남아 있으면 먼저 컨테이너를 삭제해야 합니다.

컨테이너의 실행 상태

docker ps -a 의 STATUS 열에 표시되는 값입니다. 상태에 따라 다시 켤 수 있는지가 달라집니다.

  • Created생성됨

    컨테이너가 만들어졌지만 아직 시작되지 않은 상태입니다. docker create로 만들면 이 상태가 됩니다.

  • Up실행 중

    정상적으로 동작 중인 상태입니다. docker ps 목록에 기본으로 표시됩니다.

  • Paused일시 중지

    프로세스가 멈춰 있지만 메모리 상태는 그대로 유지됩니다. docker unpause로 즉시 재개합니다.

  • Exited종료됨

    컨테이너가 멈춘 상태입니다. 삭제된 것은 아니라서 docker start로 다시 켤 수 있습니다.

상태를 바꾸는 명령어

  • docker start 컨테이너Exited · Created → Up
  • docker stop 컨테이너Up → Exited (정상 종료)
  • docker pause 컨테이너Up → Paused (일시 중지)
  • docker unpause 컨테이너Paused → Up (재개)
  • docker rm 컨테이너Exited 상태의 컨테이너 삭제
Exited는 삭제가 아닙니다. 종료된 컨테이너도 목록에 그대로 남아 디스크를 차지합니다. docker ps -a로 확인한 뒤 필요 없는 것은 docker rm으로 정리하세요.

13. 컨테이너 실행 명령어

컨테이너를 실행하는 명령은 두 가지입니다. docker run은 이미지로 컨테이너를 새로 만들어 실행하고, docker start는 이미 만들어 둔 컨테이너를 다시 실행합니다.

구분docker rundocker start
역할이미지로 컨테이너를 새로 만들어 실행이미 있는 컨테이너를 다시 실행
대상이미지 이름 (예: ubuntu)컨테이너 이름 또는 ID
실행할 때마다새 컨테이너가 계속 늘어남같은 컨테이너를 재사용 (내부 파일 유지)
docker start 컨테이너이름
docker run [옵션] 이미지이름 [실행할명령]

docker run 주요 옵션

  • -iinteractive

    컨테이너의 입력(표준 입력)을 열어 둡니다. 이 옵션이 없으면 명령을 입력해도 컨테이너가 받지 못합니다.

  • -ttty

    가상 터미널을 할당합니다. 셸 프롬프트가 보이고 명령을 입력할 수 있는 환경이 됩니다.

  • --name컨테이너 이름

    컨테이너에 이름을 붙입니다. 지정하지 않으면 도커가 임의의 이름을 만듭니다.

  • -ddetach

    백그라운드에서 실행합니다. 터미널이 컨테이너에 묶이지 않아 계속 다른 작업을 할 수 있습니다.

  • --rm자동 삭제

    컨테이너가 종료되면 자동으로 삭제합니다. 잠깐 테스트할 때 찌꺼기를 남기지 않습니다.

  • -ppublish (포트)

    호스트와 컨테이너의 포트를 연결합니다. 형식은 호스트포트:컨테이너포트입니다.

  • -vvolume (디렉터리)

    호스트와 컨테이너의 디렉터리를 연결합니다. 컨테이너를 지워도 데이터가 남습니다.

-i -t는 거의 항상 같이 쓰기 때문에 -it로 붙여서 씁니다. 컨테이너 안에서 직접 명령을 입력하려면 이 조합이 필요합니다.

실행 예제

  • Ubuntu 컨테이너에 바로 접속

    -i와 -t를 붙여 -it로 씁니다. 실행과 동시에 컨테이너 셸로 들어갑니다.

    docker run -it ubuntu
  • 이름을 붙여서 실행

    이름을 지정하면 다음부터 docker start myubuntu처럼 이름으로 다룰 수 있습니다.

    docker run -it --name myubuntu ubuntu
  • 웹서버를 백그라운드로 실행

    -d로 백그라운드 실행하고, 호스트 8080 포트를 컨테이너 80 포트에 연결합니다.

    docker run -d -p 8080:80 --name my-web nginx
  • 한 번만 쓰고 자동 삭제

    exit로 나오는 순간 컨테이너가 사라집니다. 간단한 확인 작업에 적합합니다.

    docker run -it --rm ubuntu
run을 반복하면 컨테이너가 계속 쌓입니다. 같은 작업을 이어서 하려면 새로 run 하지 말고 docker start + docker exec -it로 기존 컨테이너에 다시 들어가세요.

14. Dockerfile 작성

Dockerfile은 이미지를 만드는 설치 설명서입니다. 위에서 아래로 한 줄씩 실행되며, 각 명령이 하나의 레이어로 쌓여 이미지가 완성됩니다.

주요 명령어

  • FROM베이스 이미지 지정 (반드시 첫 줄)

    어떤 이미지 위에서 시작할지 정합니다. 모든 Dockerfile은 FROM으로 시작합니다.

    FROM node:20-slim
  • WORKDIR작업 디렉터리 지정

    이후 명령이 실행될 기준 경로입니다. 폴더가 없으면 자동으로 만들어 줍니다.

    WORKDIR /app
  • COPY호스트 파일을 이미지 안으로 복사

    빌드하는 위치(빌드 컨텍스트)의 파일을 이미지로 넣습니다. 가장 많이 쓰는 복사 명령입니다.

    COPY package*.json ./
    COPY . .
  • ADD복사 + 압축 해제 · URL 다운로드

    COPY 기능에 tar 자동 해제와 URL 다운로드가 더해진 명령입니다. 특별한 이유가 없으면 COPY를 쓰세요.

    ADD app.tar.gz /app
  • RUN이미지를 만들 때(빌드 중) 실행할 명령

    패키지 설치처럼 이미지에 남아야 하는 작업을 실행합니다. 실행할 때마다 새 레이어가 생깁니다.

    RUN apt-get update && apt-get install -y curl
    RUN npm ci
  • ENV환경 변수 설정 (컨테이너 실행 후에도 유지)

    이미지 안에 남는 환경 변수입니다. 실행할 때 docker run -e로 덮어쓸 수 있습니다.

    ENV NODE_ENV=production
  • ARG빌드할 때만 쓰는 변수

    빌드 시점에만 유효합니다. docker build --build-arg VERSION=22 로 값을 넘깁니다.

    ARG VERSION=20
    FROM node:${VERSION}-slim
  • EXPOSE컨테이너가 사용하는 포트를 알림

    문서 역할입니다. 실제 연결은 실행할 때 docker run -p 옵션으로 해야 합니다.

    EXPOSE 3000
  • VOLUME데이터를 보관할 경로 지정

    해당 경로를 볼륨으로 지정해 컨테이너를 지워도 데이터가 남도록 합니다.

    VOLUME ["/data"]
  • USER명령을 실행할 사용자 변경

    기본은 root입니다. 보안을 위해 일반 사용자로 낮춰서 실행하는 것을 권장합니다.

    USER node
  • CMD컨테이너 시작 시 실행할 기본 명령

    docker run 뒤에 명령을 적으면 이 값이 교체됩니다. Dockerfile에 하나만 유효합니다.

    CMD ["node", "server.js"]
  • ENTRYPOINT항상 실행되는 고정 명령

    docker run 뒤에 적은 값은 교체가 아니라 인자로 덧붙습니다. CMD와 함께 쓰면 기본 인자를 정할 수 있습니다.

    ENTRYPOINT ["node"]

RUN · CMD · ENTRYPOINT 차이

명령실행 시점용도실행 시 인자
RUN이미지 빌드 중패키지 설치 등 이미지에 남는 작업해당 없음
CMD컨테이너 시작 시기본 실행 명령docker run 인자로 교체됨
ENTRYPOINT컨테이너 시작 시항상 실행되는 고정 명령인자로 덧붙음 (교체 아님)

전체 예제

Node.js 앱을 이미지로 만드는 기본 형태입니다. 의존성을 먼저 설치하고 소스를 나중에 복사하는 순서가 핵심입니다.

# 1. 베이스 이미지
FROM node:20-slim

# 2. 작업 디렉터리
WORKDIR /app

# 3. 의존성 먼저 복사 (캐시 활용)
COPY package*.json ./
RUN npm ci

# 4. 나머지 소스 복사
COPY . .

# 5. 환경 변수와 포트
ENV NODE_ENV=production
EXPOSE 3000

# 6. 컨테이너 시작 명령
CMD ["node", "server.js"]

빌드하고 실행하기

  1. Dockerfile 작성

    프로젝트 최상위에 Dockerfile이라는 이름(확장자 없음)으로 만듭니다.

    vim Dockerfile
  2. 이미지 빌드

    -t는 이미지 이름(태그)입니다. 맨 뒤의 점(.)은 현재 폴더를 빌드 컨텍스트로 쓴다는 뜻이며 생략할 수 없습니다.

    docker build -t my-app:1.0 .
    docker images
  3. 빌드한 이미지로 컨테이너 실행

    EXPOSE만으로는 접속되지 않으므로 -p로 호스트 포트를 연결합니다.

    docker run -d -p 3000:3000 --name my-app my-app:1.0

작성 팁

  • 변경이 적은 명령을 위에 두세요. 레이어 캐시가 재사용되어 빌드가 빨라집니다.
  • 의존성 파일(package.json 등)을 먼저 COPY하고 설치한 뒤, 소스 전체를 COPY하세요.
  • apt-get install은 update와 && 로 한 줄에 묶어야 오래된 캐시로 설치되는 문제를 막습니다.
  • .dockerignore에 node_modules, .git을 넣어 빌드 컨텍스트 용량을 줄이세요.
  • CMD는 배열 형식(exec form)으로 쓰는 것이 종료 신호 처리에 안전합니다.
EXPOSE는 포트를 열어 주지 않습니다. 문서 역할만 하므로, 외부에서 접속하려면 실행할 때 -p 3000:3000처럼 포트를 직접 연결해야 합니다.

15. docker-compose 기본 사용법

웹서버와 데이터베이스처럼 여러 컨테이너를 함께 써야 할 때, 실행 옵션을 docker-compose.yml 파일 하나에 적어 두고 명령 한 번으로 전부 실행하는 도구입니다.

구분docker rundocker-compose
실행 방법컨테이너마다 docker run 을 각각 입력docker-compose up -d 한 번
설정 관리긴 옵션을 매번 기억해서 입력docker-compose.yml 파일에 기록 (Git으로 공유)
컨테이너 간 통신네트워크를 직접 만들어 연결같은 네트워크에 자동 연결, 서비스 이름으로 접근

docker-compose.yml 주요 항목

  • services실행할 컨테이너들을 정의하는 최상위 항목

    여기 아래에 적는 이름(web, db 등)이 곧 서비스 이름이자 컨테이너를 부르는 이름이 됩니다.

  • image사용할 이미지 지정

    Docker Hub에서 받아올 이미지를 적습니다. 직접 빌드하지 않을 때 사용합니다.

  • buildDockerfile로 직접 빌드

    image 대신 사용합니다. 값으로 Dockerfile이 있는 경로(보통 .)를 적습니다.

  • ports포트 연결 (docker run -p 와 동일)

    "호스트포트:컨테이너포트" 형식으로 적습니다.

  • volumes디렉터리·볼륨 연결 (docker run -v 와 동일)

    호스트 경로나 이름 있는 볼륨을 컨테이너 경로에 연결해 데이터를 보존합니다.

  • environment환경 변수 설정

    DB 비밀번호처럼 컨테이너에 전달할 값을 적습니다. .env 파일로 분리할 수도 있습니다.

  • depends_on실행 순서 지정

    먼저 떠야 하는 서비스를 적습니다. 시작 순서만 보장하고, 준비 완료까지 기다리지는 않습니다.

  • restart재시작 정책

    always나 unless-stopped를 주면 서버 재부팅이나 오류 종료 후에도 자동으로 다시 실행됩니다.

작성 예제

웹 앱(web)과 데이터베이스(db)를 함께 띄우는 구성입니다. 들여쓰기는 공백 2칸으로 맞추고 탭은 쓰지 않습니다.

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=appdb
    volumes:
      - db-data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  db-data:

web 컨테이너에서 데이터베이스에 접속할 때는 IP 대신 서비스 이름인 db를 호스트명으로 씁니다. compose가 두 컨테이너를 같은 네트워크에 자동으로 연결해 주기 때문입니다.

주요 명령어

모든 명령은 docker-compose.yml이 있는 폴더에서 실행합니다.

  • docker-compose up -d

    정의된 컨테이너를 모두 백그라운드로 실행

    이미지가 없으면 받아오거나 빌드까지 함께 진행합니다. -d를 빼면 로그가 터미널에 계속 출력됩니다.

  • docker-compose ps

    이 프로젝트 컨테이너 상태 확인

    compose 파일에 정의된 서비스만 골라서 보여 줍니다.

  • docker-compose logs -f web

    로그 실시간 확인

    서비스 이름을 생략하면 전체 로그가 섞여서 나옵니다. -f는 실시간 추적입니다.

  • docker-compose exec web /bin/bash

    실행 중인 컨테이너 안으로 들어가기

    docker exec와 같지만 컨테이너 이름 대신 서비스 이름을 씁니다.

  • docker-compose down

    컨테이너와 네트워크 정리

    중지 후 삭제까지 합니다. 볼륨까지 지우려면 -v를 붙이는데, 데이터가 사라지니 주의하세요.

  • docker-compose build

    이미지 다시 빌드

    Dockerfile을 수정했을 때 사용합니다. up --build로 빌드와 실행을 한 번에 할 수도 있습니다.

  • docker-compose restart web

    특정 서비스만 재시작

    서비스 이름을 생략하면 전체가 재시작됩니다.

docker-compose 와 docker compose 는 같은 기능입니다. 하이픈이 있는 쪽은 별도로 설치하는 예전 방식(standalone), 띄어쓴 쪽은 도커에 내장된 최신 방식입니다. 설치 환경에 맞는 쪽을 쓰면 됩니다.
down -v 는 데이터를 지웁니다. docker-compose down은 컨테이너만 정리하지만, -v를 붙이면 볼륨에 저장된 데이터베이스 내용까지 함께 삭제됩니다.

실전 예제: FastAPI + nginx

FastAPI 앱을 nginx 뒤에 두는 구성입니다. 외부 요청은 nginx(80 포트)가 받아서 내부의 backend(8000 포트)로 넘겨 줍니다.

  1. 브라우저
  2. nginx :80
  3. backend :8000
  4. FastAPI 응답

폴더 구조

프로젝트/
├── backend/
│   ├── main.py           # FastAPI 앱
│   ├── pyproject.toml    # 의존성 목록
│   ├── uv.lock           # 잠금 파일
│   └── Dockerfile        # backend 이미지 빌드 설명서
├── nginx/
│   └── nginx.conf        # 리버스 프록시 설정
├── .env                  # 환경 변수 (backend가 사용)
└── docker-compose.yml    # 두 서비스를 함께 실행

backend/Dockerfile

파이썬 패키지 관리 도구인 uv로 의존성을 설치한 뒤 uvicorn으로 앱을 실행합니다.

FROM python:3.13

# 공식 이미지에 uv가 없으므로 바이너리만 복사
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /app

COPY pyproject.toml uv.lock ./

ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
# 대문자 UV가 아니라 소문자 uv 명령 사용
RUN uv sync --frozen --no-dev --no-install-project

COPY . .

ENV PATH="/app/.venv/bin:$PATH"

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

nginx/nginx.conf

80 포트로 들어온 요청을 upstream fastapi(= backend:8000)로 전달하는 리버스 프록시 설정입니다.

user nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile on;                    # 응답을 보낼 때 user 영역 buffer 가 아닌, kernel file buffer를 사용
    keepalive_timeout 65;

    upstream fastapi {
        server backend:8000;
    }

    server {
        listen 80;

        location / {
            proxy_pass         http://fastapi;
            proxy_redirect     off;
            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-Host $server_name;
        }
    }
}

docker-compose.yml

backend는 Dockerfile로 빌드하고, nginx는 공식 이미지를 그대로 사용하면서 설정 파일만 연결합니다.

services:
  backend:
    build: ./backend
    env_file:
      - .env

  nginx:
    image: nginx
    ports:
      - 80:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - backend

눈여겨볼 점

  • backend는 포트를 열지 않습니다

    ports 설정이 없어 외부에서 직접 접근할 수 없습니다. 오직 nginx를 거쳐야만 접속되므로 더 안전합니다.

  • nginx는 서비스 이름으로 backend를 찾습니다

    nginx.conf의 server backend:8000 에서 backend는 compose의 서비스 이름입니다. IP를 몰라도 이름만으로 통신합니다.

  • 설정 파일은 볼륨으로 연결합니다

    nginx.conf를 이미지에 굽지 않고 -v로 연결해 두면, 설정을 고친 뒤 재시작만 하면 반영됩니다.

  • 의존성 파일을 먼저 복사합니다

    Dockerfile에서 pyproject.toml과 uv.lock을 먼저 COPY하고 uv sync를 실행합니다. 소스만 바뀌면 설치 단계가 캐시로 재사용됩니다.

  • uvicorn은 0.0.0.0으로 실행합니다

    127.0.0.1로 띄우면 컨테이너 안에서만 접속됩니다. 다른 컨테이너(nginx)가 접근하려면 0.0.0.0이어야 합니다.

실행하기

docker-compose up -d --build
docker-compose ps
docker-compose logs -f backend

브라우저에서 http://서버주소 로 접속하면 nginx를 거쳐 FastAPI 응답이 보입니다. 80 포트라서 주소에 포트 번호를 붙이지 않아도 됩니다.

.env 파일이 없으면 실행되지 않습니다. env_file에 적은 파일은 반드시 있어야 합니다. 비밀 값이 들어가므로 .gitignore에 추가해 저장소에 올라가지 않게 하세요.