Docker 시작하기 - 1
Docker 시작하기

시작하려면 일단 도커 데스크톱을 다운받아야한다.

Why DOCKER?
이식성때문이다. 애플리케이션 버전 차이로 인한 충돌이나 버그 등을 방지하여 내 컴퓨터에선 잘 되는데 서버에서는 안 된다 라는 상황이 줄어든다.
실제로 교육 시 명세서와 내 로컬에 깔려 있는 Node.js 버전이 달라 충돌난 경우가 있는데, 도커를 사용하면 버전을 명시해서 팀원에게 배포가 되기 때문에 이렇게 설정 차이로 인해 작동되지 않는 상황 대부분을 방지한다.
프로젝트 상황 7주간의 프로젝트
6명의 팀원
서로 다른 환경 및 버전으로 충돌나는 상황을 최소화하기 위해 docker를 도입, 컨테이너 환경에서 CI/CD를 구축하는 게 좋다고 판단했다.
도커의 이미지 최적화 방법
- 캐싱을 활용하여 동일한 빌드 내역에 대해 다시 빌드하지 않는다.
- dockerignore 파일로 이미지의 크기를 줄일 수 있다. (ex. node_moduels, .git)
이미지 저장소 관리에 있어 좋은 규약
- 이미지의 태그와 버전 관리를 철저히 해야한다. (ex. latest 가 아닌 v1.0.1 등 명시)
- 불필요한 이미지를 정기적으로 정리한다.
- 민감한 정보를 포함하는 이미지는 퍼블릭 레지스트리 말고 프라이빗 레지스트리를 사용해 보안을 강화한다.
- 자동화된 CI/CD 파이프라인을 활용하여 이미지를 빌드하고 배포하는 과정을 자동화한다.
DOCKER COMPOSE와 Dockerfile의 차이
처음 도커에 대해 배우며 둘의 차이가 궁금해졌다.
version: '3'
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- backend
backend:
build: ./backend
environment:
- DATABASE_URL=mongodb://db:27017
ports:
- "4000:4000"
depends_on:
- db
db:
image: mongo:latest
ports:
- "27017:27017"
Docker compose는 애플리케이션의 전체적인 구성을 나타내는 파일이다.
Dockerfile은 애플리케이션의 docker 이미지를 정의하는 텍스트 파일이다. 아래는 node를 사용하는 dockerfile 스크립트이다.
# 1. Node.js 기본 이미지를 사용
FROM node:16
# 2. 이미지 메타데이터 (LABEL) 설정
LABEL maintainer="your-email@example.com"
LABEL version="1.0"
LABEL description="Node.js application with Docker"
# 3. 작업 디렉토리 생성 및 설정
WORKDIR /app
# 4. 로컬의 package.json 및 package-lock.json을 컨테이너에 복사
COPY package*.json ./
# 5. 의존성 설치
RUN npm install
# 6. 애플리케이션 소스 코드 복사
COPY . .
# 7. 환경 변수 설정 (예: 포트, 앱 모드)
ENV NODE_ENV=production
ENV PORT=3000
# 8. 애플리케이션 실행 포트 공개 (컨테이너 외부에서 접근할 수 있도록 설정)
EXPOSE 3000
# 9. 컨테이너 시작 시 실행될 명령어 (애플리케이션 실행)
CMD ["node", "app.js"]
dockerfile은 FROM 으로 시작한다. 또한 LABEL과 ENV로 환경 변수나 maintainer, 작성자 정보 등 메타데이터를 작성할 수 있다. cmd는 한 dockerfile 당 한 번만 가능하다.
EXPOSE는 정보제공용으로 실제로 포트를 개방하려면 docker run 명령어에서 포트 매핑을 설정해야한다.
이미지 최적화 방법의 캐싱은 위 dockerfile에 package.json 파일이 변경되지 않았을 때, npm install 단계가 캐시에서 제공되어 빌드 속도가 빨라지는 것을 의미한다.
CI/CD 파이프라인 구축
경험 : 기존에 프로젝트를 GITHUB을 통해 진행해 PUSH, PULL로 협업할 때, .gitignore에 올라간 .env 파일이 자꾸 사라지는 바람에 개발을 하는데 반복되는 귀찮은 작업이 발생했다. 이를 방지하기 위해 자동화해야겠다고 생각했다.
현재 내가 수강하는 부트캠프에서는 GITLAB을 지원하기에 gitlab에서 지원하는 CI/CD를 사용하는 게 좋다고 판단했다.
일단 지원하는 gitlab이 무료 버전(무료 버전은 400분/월 제한)을 알아내야 겠다.
-> 무료버전이며 아마 제한적일 것이다. 보통 Jenkins를 많이 씀. 이라는 답변을 얻었다.
서버를 따로 구축해야하는 Jenkins 보다 github actions을 쓰는게 좋다고 판단했다.
멀티스테이지 빌드 방법
단일 dockerfile로 관리할 수 있어 프로젝트의 복잡성을 줄이고, 유지보수를 쉽게 한다.
아래는 자바를 멀티스테이지 빌드 방법으로 dockerfile을 작성한 예이다.
# 1단계: 빌드 단계
FROM maven:3.8.6-openjdk-11 AS build
# 작업 디렉토리 설정
WORKDIR /app
# pom.xml과 소스 코드 복사
COPY pom.xml .
COPY src ./src
# Maven 빌드 실행 (디펜던시 다운로드 및 프로젝트 빌드)
RUN mvn clean install -DskipTests
# 2단계: 실행 단계
FROM openjdk:11-jre-slim
# 작업 디렉토리 설정
WORKDIR /app
# 빌드된 .jar 파일을 이전 스테이지에서 복사
COPY --from=build /app/target/myapp.jar myapp.jar
# 애플리케이션 포트 노출
EXPOSE 8080
# 애플리케이션 실행 명령
CMD ["java", "-jar", "myapp.jar"]

컨테이너를 선택할 때, container id를 전부 칠 필요 없고 앞 두 글자만 해도 알아서 인식해서 명령어를 수행한다.
create 명령어는 컨테이너를 만들기만 하고, run 은 없으면 작성하고 실행한다. 위 예시는 nginx 이미지를 자동으로 pull하는 것
내부 접근 명령어
docker exec -it my-nginx /bin/bash
탈출 명령어 ctrl + d
컨테이너 재시작 정책
예기치 않은 종료나 시스템 재부팅 후에도 컨테이너가 자동으로 다시 실행되도록 할 수 있다.
docker run --name my-nginx --restart always -d -p 8080:80 nginx:latest
위 명령어는 컨테이너가 종료될 때마다 자동으로 재시작하도록 설정한다.

Docker 네트워크 개념
컨테이너는 기본적으로 호스트 시스템과 격리된 네트워크 환경에서 실행된다. 기본 네트워크 드라이버로는 bridge 네트워크이며 컨테이너 간 격리된 네트워크를 제공한다.
- 동일한 브리지 네트워크에 있는 컨테이너 간의 통신을 가능하게 한다.
Host 네트워크
컨테이너가 호스트 시스템의 네트워크 스택을 공유하도록 설정한다. 이를 통해 컨테이너는 호스트 시스템과 동일한 네트워크 인터페이스와 ip 주소를 사용하게 된다. 이 네트워크는 성능이 중요하거나, 네트워크 설정을 간소화하고자 할 때 유용하다.
Overlay 네트워크
여러 Docker 데몬 간의 네트워크를 구성하여, 분산된 컨테이너 간의 통신을 가능하게 한다. 주로 Docker swarm이나 Kubernetes 와 같은 오케스트레이션 도구와 함께 사용된다. 각 노드 간의 터널링을 통해 네트워크 격리를 유지하면서도 분산 환경에서의 통신을 지원한다.
컨테이너는 네트워크에 연결되면 고유한 네트워크 인터페이스와 IP주소를 할당한다.
docker inspect my-container
위 명령어는 컨테이너의 상세 정보를 출력하며, 네트워크 인터페이스와 IP 주소를 확인할 수 있습니다.

docker compose 파일에 network 기재를 생략해도 자동으로 bridge 네트워크가 할당된다. 보통 사용자 설정 네트워크를 만들기 위해 사용된다.
포트 매핑
포트 매핑을 통해 격리된 호스트와 컨테이너를 연결한다.
docker run --name my-container -p 8080:80 -d nginx:latest
포트 매핑을 설정하려면 -p 옵션을 사용하고 위 예시는 호스트의 8080 포트를 컨테이너의 80번 포트에 매핑하는 것이다.
네트워크 보안
네트워크를 격리를 유지하며 보안을 강화하기 위해 아래 방법을 사용할 수 있다.
- 사용자 정의 네트워크를 사용하여 컨테이너 간의 통신을 세밀하게 제어한다.
- 네트워크 정책을 설정하여 특정 컨테이너 간의 통신을 허용하거나 차단한다.
- TLS 인증서를 사용해 네트워크 트래픽을 암호화한다.
데이터 지속성 관리
Docker 컨테이너는 기본적으로 상태가 없는 (stateless)방식으로 설계되어 중지되거나 삭제될 때 그 내부의 모든 데이터가 사라진다. 그러나 실제 애플리케이션에서는 데이터를 영구적으로 저장하고 관리해야 하는 경우가 많다.
- 애플리케이션의 데이터 손실 방지 가능
- 컨테이너 재시작이나 업데이트 후에도 데이터 유지 가능
- 데이터를 백업하고 복원할 수 있다.
데이터 저장 위치와 사용 목적에 따라,
- 볼륨(Volumes)
- 바인드 마운트(Bind Mounts)
- tmpfs 마운트(tmpfs Mounts)
등이 있다.