소개
컨테이너는 애플리케이션을 패키징하고 실행하지만 패키징만으로 안전한 프로세스 계정이 선택되지는 않습니다. 준비된 이 DeskLite 작업자는 작은 티켓 보고서를 생성하며 현재 root로 실행됩니다.
최소한의 Docker 작업 흐름을 배운 다음, root가 아닌 계정과 작업에 필요한 파일 접근 권한만 사용하도록 작업자를 다시 빌드하고 실행합니다. Docker와 고정된 기본 이미지가 준비되어 있습니다. 레지스트리 계정이나 사전 Docker 경험은 필요하지 않습니다.
준비된 컨테이너 프로젝트 살펴보기
이 단계에서는 보고 작업을 구성하는 파일을 파악합니다.
이미지는 패키징된 파일 시스템과 시작 설정입니다. 컨테이너는 자체 프로세스와 쓰기 가능한 파일 시스템 계층을 갖춘 이미지의 실행 인스턴스입니다. Dockerfile은 이미지를 빌드하는 방법을 설명합니다. ticket-report:practice와 같은 태그는 이미지에 붙이는 편리한 이름이며, 다시 빌드한 후 새 이미지를 가리키도록 옮길 수 있습니다.
cd /home/labex/project/ticket-worker
ls
Dockerfile, worker.sh, input, output이 예상됩니다. 준비된 작업자는 CSV 행을 읽고 그 개수를 보고서에 쓴 다음, 살펴볼 수 있도록 계속 실행됩니다.
cat input/tickets.csv
헤더 뒤에 티켓 행 세 개가 있습니다. 예상 보고서는 tickets=3입니다.
cat Dockerfile
FROM은 캐시된 기본 이미지를 선택하고, WORKDIR은 이미지의 작업 디렉터리를 선택하며, COPY는 작업자를 추가합니다. USER root는 프로세스 계정을 선택합니다. CMD는 목록으로 표현된 기본 시작 명령입니다. USER만 변경하게 됩니다.
기본 태그 desklite-base:1.37.0은 고정된 BusyBox 이미지에서 이미 캐시되어 있습니다. 이후 모든 빌드와 실행은 로컬 파일을 사용합니다. Docker 명령은 이 실습의 데몬에 접근하기 위해 sudo를 사용합니다. 이 호스트 수준 접근은 강력한 권한이며, 컨테이너 안에서 줄이려는 애플리케이션 계정의 권한과는 별개입니다.
기존 작업자 빌드하고 실행하기
이 단계에서는 동작하지만 권한이 과도한 기준 상태를 확인합니다.
docker build는 Dockerfile과 빌드 컨텍스트, 즉 COPY에서 사용할 수 있는 파일이 있는 디렉터리를 읽습니다. 여기서 .은 현재 디렉터리를 의미합니다. -t는 로컬 이미지 태그를 지정합니다.
sudo docker build -t ticket-report:practice .
빌드가 성공해야 합니다. 이미지는 존재하지만 아직 실행 중인 작업자는 없습니다.
docker run은 컨테이너를 만듭니다. -d는 백그라운드에서 실행하고 --name은 재사용할 수 있는 이름을 부여합니다. 이 보고서에는 파일만 필요하므로 --network none으로 네트워크를 비활성화합니다.
바인드 마운트는 호스트 경로를 컨테이너 경로에서 사용할 수 있게 합니다. 각 --mount는 유형, 원본(src), 대상(dst)을 설명합니다. 이 기준 상태에는 readonly 옵션이 없으므로 두 디렉터리 모두 쓰기 가능하게 노출됩니다. 나중에 입력을 제한합니다.
sudo docker run -d --name ticket-worker --network none --mount type=bind,src=/home/labex/project/ticket-worker/input,dst=/data/input --mount type=bind,src=/home/labex/project/ticket-worker/output,dst=/data/output ticket-report:practice
Docker는 컨테이너 ID를 출력합니다. 이는 작업자의 사용자 ID가 아닙니다. 기존 작업자가 보고서를 생성해야 하며 다음으로 이를 확인합니다.
실제 실행 계정 확인하기
이 단계에서는 성공적인 보고서 생성과 최소 권한 실행 상태를 구분합니다.
docker ps는 실행 중인 컨테이너를 나열합니다.
sudo docker ps
ticket-worker를 찾으세요. docker logs는 애플리케이션의 표준 출력을 읽습니다.
sudo docker logs ticket-worker
worker uid=0과 tickets=3이 예상됩니다. UID 0은 root입니다. 업무 작업은 동작하지만 필요 이상의 권한을 사용합니다.
docker exec는 실행 중인 이 컨테이너에서 확인 명령을 실행합니다. id -u는 실제 적용되는 숫자 사용자 ID를 출력합니다.
sudo docker exec ticket-worker id -u
다시 0이 예상됩니다. docker inspect는 설정을 읽습니다. --format은 필드 하나를 선택합니다. 따옴표로 묶인 템플릿을 정확히 복사하세요. .Config.User는 설정된 사용자입니다.
sudo docker inspect --format '{{.Config.User}}' ticket-worker
root가 예상됩니다. 이미지 설정과 실제 프로세스 확인 결과가 일치합니다. 파일에 "non-root"라고 적혀 있다는 것만으로는 이런 증거가 되지 않습니다.
준비된 비 root 사용자로 다시 빌드하기
이 단계에서는 이미지의 기본 애플리케이션 계정을 설정합니다.
캐시된 기본 이미지에는 UID가 65534인 nobody가 이미 있습니다. 사용자 생성이나 비밀번호는 필요하지 않습니다. Dockerfile을 여세요.
nano Dockerfile
USER 지시문만 변경하세요. 전체 파일은 다음과 같아야 합니다.
FROM desklite-base:1.37.0
WORKDIR /app
COPY worker.sh /app/worker.sh
USER nobody
CMD ["sh", "/app/worker.sh"]
Ctrl+O, Enter로 저장하고 Ctrl+X로 종료하세요.
sudo docker build -t ticket-report:practice .
이제 태그는 다시 빌드한 이미지를 식별합니다. 기존 컨테이너가 다시 빌드한 이미지로 자동 전환되지는 않습니다. 이전 root 컨테이너는 여전히 이전 인스턴스입니다. 다음 단계에서 이를 교체하고 새 계정에 필요한 출력 접근 권한을 부여합니다.
제한된 마운트로 컨테이너 교체하기
이 단계에서는 root가 아닌 계정과 정상 동작하는 파일 권한을 함께 적용합니다.
docker rm은 컨테이너를 제거합니다. -f는 실행 중이라면 먼저 중지합니다. 이 연습에서 지정한 이름의 컨테이너만 제거하세요.
sudo docker rm -f ticket-worker
보고서 디렉터리는 처음에 root 소유였습니다. 파일 권한 실습과 마찬가지로 root가 아닌 프로세스가 출력을 쓰려면 소유권과 권한이 필요합니다. chown -R은 디렉터리와 기존 보고서의 소유권을 재귀적으로 UID 및 GID 65534로 변경합니다. 모드 750은 모든 사용자에게 쓰기를 허용하지 않으면서 소유자에게 모든 접근 권한을 부여합니다.
sudo chown -R 65534:65534 output
sudo chmod 750 output
이제 대체 컨테이너를 만드세요. 입력 마운트에는 readonly를 추가하고 출력 마운트는 쓰기 가능하게 유지합니다. 이렇게 하면 보고서 생성을 허용하면서 입력 수정을 방지할 수 있습니다.
sudo docker run -d --name ticket-worker --network none --mount type=bind,src=/home/labex/project/ticket-worker/input,dst=/data/input,readonly --mount type=bind,src=/home/labex/project/ticket-worker/output,dst=/data/output ticket-report:practice
새 컨테이너 ID가 예상됩니다. 이제 이미지가 root가 아닌 사용자를 제공하므로 실행 시 root로 재지정할 필요가 없습니다.
계정, 접근 권한, 업무 출력 검증하기
이 단계에서는 대체 컨테이너에서 결과의 세 부분을 모두 확인합니다.
sudo docker exec ticket-worker id -u
65534가 예상됩니다. 작업자의 실제 출력을 확인하세요.
sudo docker logs ticket-worker
worker uid=65534와 tickets=3이 예상됩니다. 컨테이너를 통해 실제 출력 파일을 읽으세요.
sudo docker exec ticket-worker cat /data/output/report.txt
tickets=3이 예상됩니다. 이는 권한을 줄여도 작업이 깨지지 않았음을 입증합니다.
마운트를 확인하세요. 따옴표로 묶인 템플릿은 .Mounts를 선택하고 JSON 형식으로 표시합니다.
sudo docker inspect --format '{{json .Mounts}}' ticket-worker
/data/input에 "RW":false, /data/output에 "RW":true가 있는지 찾으세요. RW는 읽기/쓰기를 의미합니다. 같은 입력 바이트를 읽을 수 있지만 마운트가 쓰기를 거부합니다.
이 제한을 테스트하기 위해 무해한 파일 생성을 시도하세요. touch는 허용된다면 빈 파일을 만듭니다.
sudo docker exec ticket-worker touch /data/input/probe.txt
Read-only file system 오류와 0이 아닌 종료 코드가 예상됩니다. 이 거부 하나는 의도된 결과입니다. 이를 없애려고 입력을 쓰기 가능하게 만들지 마세요. 보고서는 이미 읽기 전용 입력으로 성공했습니다.
최종 작업자가 계속 실행 중인지 확인하기
이 단계에서는 의도한 작업자를 확인할 수 있는 상태로 남겨 둡니다.
sudo docker ps
ticket-worker라는 이름의 컨테이너가 실행 중이어야 합니다. 설정된 사용자와 보고서를 한 번 더 확인하세요.
sudo docker inspect --format '{{.Config.User}}' ticket-worker
sudo docker exec ticket-worker cat /data/output/report.txt
nobody와 tickets=3이 예상됩니다. 교육 과정의 검증은 실행 중인 이미지와 전달용 태그도 비교하고, 실제 작업자 PID를 확인하며, 원본 입력과 출력을 테스트합니다. 컨테이너를 교체하지 않고 Dockerfile만 다시 빌드해서는 충분하지 않습니다.
이 연습은 애플리케이션 계정과 파일 접근 권한을 줄입니다. Docker 데몬 접근 자체를 일반 사용자 권한으로 만들거나 운영 환경의 완전한 컨테이너 보안 강화 정책을 확립하는 것은 아닙니다.
요약
root가 아닌 애플리케이션 계정으로 실제 이미지를 빌드하고, 입력을 제한하면서 출력에 필요한 접근 권한을 갖추어 해당 이미지를 실행했으며, 실제 프로세스와 보고서를 검증했습니다. 이미지 설정, 실행 시 계정, 마운트 모드, 기능 동작이 모두 최종 상태에 기여합니다.



