[AWS] docker compose https 인증하기 - nginx, let's encrypt

docker compose에서 nginx, certbot 컨테이너를 사용하여 https 인증하기
dustjq1005 2025년 06월 28일 2025년 07월 08일 수정됨

환경

  • AWS EC2, RDS, Route53, 가이아 도메인, Docker compose, nginx

원래는 AWS에서 대신 인증처리 해주는 AWS Certificate Manager를 이용하고 있었습니다. 그런데 로드밸런싱을 사용(비용 문제)해야하는 이슈 때문에 최근에 let’s encrypt라는 https 무료 인증 툴로 변경하게 됐습니다.

로드밸런싱은 ipv4를 최소 2개를 사용하기 때문에 아무래도 비용이 추가 될 수 밖에 없습니다. 1 -> 2개는 2배이기 때문에 큰 차이가 있죠~ 그만큼 AWS CM이 무료 버전 보다는 장점이 있긴 하지만, 이용자가 없는 제 개인 사이트는 별 다른 보안은 필요 없어서 let’s encrypt를 사용하기로 했습니다.

디렉토리 구조

project-root/
│
├── docker-compose.yml
├── nginx/
│   ├── default.conf           # Nginx 설정 파일
│
├── certbot/                   # Let's Encrypt용 볼륨 마운트 경로
│
├── spring/
│   └── Dockerfile             # Spring Boot Dockerfile
│   └── your-app.jar

위에 구조는 예시일 뿐 입니다. 실제 저는 다르게 하긴 했는데 여기서 알아야할 것은 nginx/, certbot/ 디렉터리 일 것 같습니다. 볼륨 마운트 경로를 지정해야 하는데, docker-compose는 이 경로를 통해서 설정파일을 읽어서 내부로 가져가 사용합니다.

nginx 설치+ certbot 설치

sudo apt update
sudo apt install certbot
sudo apt install nginx

nginx, certbot 설치를 합니다.

docker-compose 수정

services:
  spring-app:
    container_name: spring-app
    build:
      context: ./spring
    ports:
      - "8080:8080"
    networks:
      - web

  nginx:
    image: nginx:latest
    container_name: nginx
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - spring-app
    networks:
      - web

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do sleep 1; done'"
    networks:
      - web

volumes:
  certbot-etc:
  certbot-var:

networks:
  web:
    driver: bridge

services

애플리케이션을 구성하는 컨테이너들을 정의합니다. spring-app, nginx, certbot은 컨테이너 명칭이 됩니다.

nginx 컨테이너에 volumes는 위에 설명했듯이 명시된 경로를 통해 호스트의 파일/디렉토리를 컨테이너와 공유를 합니다.

  • ./nginx/default.conf:/etc/nginx/conf.d/default.conf : ./nginx/nginx.conf를 컨테이너 내부 /etc/nginx/conf.d/default.conf로 사용하게 됩니다.

nginx.conf 작성

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name yourdomain.com www.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://spring-app:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

docker-compose.yml 파일에서 지정해준 ./data/nginx/ 경로에 nginx.conf 파일입니다. docker는 해당 파일을 자신에 내부 컨테이너에서 사용하게 됩니다.

  • yourdomain.com : 자신의 사이트 도메인을 입력합니다.
  • location /.well-known/acme-challenge/ : certbot 인증시 사용할 location입니다. let’s encrypt가 해당 도메인 주소로 HTTP 요청을 보내어 인증을 하게 됩니다.

한 가지 더 확인해야 할 것은 80포트로 접속이 가능한지 확인해야 합니다. listen 80 : 해당 포트가 열려 있지 않으면 인증이 제대로 이루어지지 않습니다.

  1. init-letsencrypt.sh 가져오기
sudo curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh

명령어를 실행해서 init-letsencrtpt.sh 파일을 가져올 수 있습니다. 저는 docker-compose.yml 파일이 있는 경로에 명령어를 실행해서 init-letsencrypt.sh 파일을 가져왔습니다.

//init-letsencrypt.sh
#!/bin/bash

if ! [ -x "$(command -v docker-compose)" ]; then
  echo 'Error: docker-compose is not installed.' >&2
    exit 1
    fi

    domains=(#domain)
    rsa_key_size=4096
    data_path="./data/certbot"
    email="#email" # Adding a valid address is strongly recommended
    staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits

    if [ -d "$data_path" ]; then
	      read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
	        if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then
			    exit
			      fi
    fi


    if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then
	      echo "### Downloading recommended TLS parameters ..."
	        sudo mkdir -p "$data_path/conf"
		  curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf"
		    curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"
		      echo
    fi

    echo "### Creating dummy certificate for $domains ..."
    path="/etc/letsencrypt/live/$domains"
    sudo mkdir -p "$data_path/conf/live/$domains"
docker compose run --rm --entrypoint "\
	  openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\
	      -keyout '$path/privkey.pem' \
	          -out '$path/fullchain.pem' \
		      -subj '/CN=localhost'" certbot
echo


echo "### Starting nginx ..."
docker compose up --force-recreate -d nginx
echo

echo "### Deleting dummy certificate for $domains ..."
docker compose run --rm --entrypoint "\
	  rm -Rf /etc/letsencrypt/live/$domains && \
	    rm -Rf /etc/letsencrypt/archive/$domains && \
	      rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echo


echo "### Requesting Let's Encrypt certificate for $domains ..."
#Join $domains to -d args
domain_args=""
for domain in "${domains[@]}"; do
	  domain_args="$domain_args -d $domain"
  done

  # Select appropriate email arg
  case "$email" in
	    "") email_arg="--register-unsafely-without-email" ;;
	      *) email_arg="--email $email" ;;
      esac

      # Enable staging mode if needed
      if [ $staging != "0" ]; then staging_arg="--staging"; fi

docker compose run --rm --entrypoint "\
	  certbot certonly --webroot -w /var/www/certbot \
	      $staging_arg \
	          $email_arg \
		      $domain_args \
		          --rsa-key-size $rsa_key_size \
			      --agree-tos \
			          --force-renewal" certbot
echo

echo "### Reloading nginx ..."
docker compose exec nginx nginx -s reload

파일을 가져오고 열어보면 위와 같이 내용이 작성되어 있는데 일부 수정을 해야 합니다. #domain, #email 로 되어있는 부분을 수정하면 됩니다.

그리고 다음 명령어들을 입력합니다.

  1. chmod +x init-letsencrypt.sh 실행 권한 부여

  2. ./init-letsencrypt.sh

인증이 완료되면 docker-compose up -d 명령어를 통해 애플리케이션을 실행합니다.


각종 오류들…

오류들이라고 하면 대부분 실행파일 권한 오류이거나.. 경로가 잘못되어서 그런 경우가 많았습니다. 그리고 한가지 조심해야할 것이 있는데..

let’s Encrypt 인증을 실패했다고 여러번 막 인증하면 안됩니다. 그러다가 일주일간 인증 못할 수도 있습니다.

Comments