오라클 클라우드 무료 서버에 Docker 올리기: Portainer 관리, Uptime Kuma 배포와 서브도메인 HTTPS 연결
· 16분 읽기
Ubuntu 24.04 ARM 서버에 Docker와 Compose를 설치하고, Portainer는 SSH 터널로만 열고, Uptime Kuma와 웹 컨테이너를 127.0.0.1에 띄워 Nginx 서브도메인과 Let's Encrypt HTTPS로 연결한 시리즈 마지막 편 기록입니다.
이 글에서 다루는 내용
- apt로 Docker와 Docker Compose v2 설치하기
- sudo 없이 docker 명령 쓰기와 docker 그룹의 의미
- Docker가 연 포트가 UFW를 거치지 않는 문제와 127.0.0.1 바인딩
- Portainer를 띄우고 SSH 터널로만 접속하기
- Compose로 Uptime Kuma와 루트 도메인용 웹 컨테이너 배포하기
- status 서브도메인에 Nginx 리버스 프록시와 HTTPS 붙이기
- Uptime Kuma로 내 사이트 감시 시작하기
3편에서 도메인과 HTTPS까지 붙였지만, 리버스 프록시가 가리키는 8080에는 아무것도 없어서 접속하면 502 Bad Gateway가 떴습니다. 이제 실제 서비스를 올릴 차례였습니다. 서버에 프로그램을 하나씩 직접 설치하면 버전이 엉키고 지울 때도 흔적이 남기 때문에, 프로그램마다 독립된 컨테이너에 담아 실행하고 언제든 지울 수 있는 Docker를 쓰기로 했습니다.
이번 글에서는 Docker와 Compose를 설치하고, 컨테이너 관리 화면인 Portainer, 사이트 가동 상태를 감시하는 Uptime Kuma, 루트 도메인용 웹 컨테이너를 띄워 3편의 Nginx 뒤에 붙였습니다. 처음 메모해 둔 -p 9000:9000 그대로였다면 관리 화면이 인터넷에 열릴 수 있었기 때문에, 컨테이너 포트는 127.0.0.1에만 열고 Portainer는 SSH 터널로 여는 방식으로 바꿨습니다. 이 글로 시리즈를 마무리합니다.

테스트 환경
- 작업 기준: 2026-10-27
- 홈 리전: Japan East (Tokyo)
- OS 이미지: Canonical Ubuntu 24.04 (aarch64)
- 셰이프: VM.Standard.A1.Flex 2 OCPU / 12GB, 부트 볼륨 200GB
- Docker: Ubuntu 저장소의 docker.io, docker-compose-v2
- 로컬: Windows 11 + cmd, Chrome
- 이전 글: 오라클 클라우드 무료 서버에 내 도메인 연결하기: DNS A 레코드, Nginx 리버스 프록시, Let’s Encrypt 무료 HTTPS
01 apt로 Docker와 Compose v2 설치
Ubuntu 24.04 저장소에는 Docker 엔진인 docker.io와 Compose 플러그인인 docker-compose-v2 패키지가 있어서, ARM64 서버에서도 apt 한 줄로 설치됩니다. docker.io는 Docker사가 직접 배포하는 docker-ce가 아니라 Ubuntu가 패키징한 것입니다. 개인 서버에서는 보안 업데이트가 apt로 다른 패키지와 함께 들어오는 쪽이 관리하기 편해서 이쪽을 골랐습니다.
sudo apt update
sudo apt install -y docker.io docker-compose-v2
# 부팅 때 자동 시작 등록과 지금 바로 시작을 한 번에
sudo systemctl enable --now docker
처음 메모에는 ca-certificates curl gnupg lsb-release를 먼저 설치하는 줄이 있었는데, 이것은 Docker 공식 저장소(docker-ce)를 등록할 때 쓰는 준비물이라 docker.io 설치에는 필요 없어 뺐습니다. 설치가 끝나면 두 버전을 확인합니다.
docker --version
docker compose version

두 명령 모두 버전이 출력되는 것으로 확인했습니다. Compose v2는 예전의 docker-compose(하이픈)가 아니라 docker compose(띄어쓰기)로 실행합니다. 컨테이너는 모두 기존 인스턴스(2 OCPU / 12GB, 부트 볼륨 200GB) 안에서 돌기 때문에 무료 한도를 넘는 OCI 비용은 생기지 않습니다.
02 sudo 없이 docker 명령 쓰기
설치 직후 ubuntu 사용자로 docker ps를 실행하면 권한 오류가 납니다. Docker 엔진과 통신하는 소켓(/var/run/docker.sock)에 root와 docker 그룹만 접근할 수 있기 때문입니다.
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
ubuntu 사용자를 docker 그룹에 넣었습니다.
sudo usermod -aG docker $USER
# 지금 셸에 바로 적용 (새로 SSH 접속하면 자동으로 적용됨)
newgrp docker
docker ps

다시 실행한 docker ps가 오류 없이 빈 컨테이너 목록을 보여 주는 것으로 확인했습니다. docker 그룹에 들어간 사용자는 컨테이너에 서버의 어떤 폴더든 마운트할 수 있어서 사실상 root와 같은 권한을 갖습니다. 그래서 이 그룹에는 서버를 관리하는 ubuntu 계정 하나만 넣었습니다.
03 Docker가 연 포트는 UFW를 거치지 않는다
컨테이너를 띄우기 전에 짚어 둔 것이 있습니다. docker run -p나 Compose의 ports로 포트를 열면 Docker가 iptables에 직접 전달 규칙을 넣고, 이 규칙은 2편에서 만든 UFW의 허용·차단 규칙보다 먼저 처리됩니다. UFW에서 22·80·443만 열어 두었더라도 -p 9000:9000은 서버 밖으로 열린다는 뜻입니다.
| 포트 표기 | 열리는 곳 |
|---|---|
-p 9000:9000 |
서버의 모든 네트워크(0.0.0.0). UFW 규칙과 상관없이 열림 |
-p 127.0.0.1:9000:9000 |
서버 안에서만. Nginx나 SSH 터널로만 닿음 |
이 서버는 OCI 보안 목록이 한 번 더 막아 주지만, 1편에서 8080을 열어 두었기 때문에 8080을 0.0.0.0으로 열면 그대로 인터넷에 노출됩니다. Nginx는 같은 서버 안에서 127.0.0.1로 컨테이너에 접속하므로, 이 글의 컨테이너는 모두 127.0.0.1:호스트포트:컨테이너포트 형식으로 열었습니다. 이제 8080은 Nginx만 쓰므로 1편에서 OCI 보안 목록에 넣은 8080 수신 규칙은 지워도 됩니다.
주의: 포트 앞에 127.0.0.1을 붙입니다
Docker로 연 포트는 UFW 규칙을 거치지 않기 때문에, Nginx 뒤에 둘 서비스와 관리 화면은 반드시 127.0.0.1에만 열어야 합니다. 호스트 포트 앞에 IP를 빼먹으면 UFW에서 막아 둔 포트라도 바깥에서 접속됩니다.
04 Portainer 띄우기
Portainer는 컨테이너 목록과 상태, 로그, CPU·메모리 사용량을 웹 화면에서 보고 컨테이너를 켜고 끌 수 있는 관리 도구입니다. 무료인 Community Edition(portainer-ce) 이미지는 ARM64도 지원해서 그대로 받았습니다. 설정은 portainer_data 볼륨에 저장해 컨테이너를 지웠다 다시 만들어도 남게 했습니다.
# Portainer 설정을 저장할 볼륨
docker volume create portainer_data
# Portainer CE 실행 (127.0.0.1에만 열기)
docker run -d \
-p 127.0.0.1:9000:9000 \
--name=portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
# 이름, 상태, 포트만 골라 보기
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
--restart=always는 서버를 재부팅하거나 컨테이너가 멈춰도 Docker가 다시 띄우게 하는 옵션입니다. docker.sock을 마운트하는 것은 Portainer가 이 서버의 Docker를 조작하기 위해서입니다.

PORTS 열에 127.0.0.1:9000->9000/tcp가 보이는 것으로 서버 안에서만 열린 것을 확인했습니다. 함께 보이는 8000/tcp, 9443/tcp는 이미지에 적힌 포트일 뿐 밖으로 열린 것은 아닙니다.
05 SSH 터널로 Portainer 접속
처음 메모에는 브라우저에서 http://서버_IP:9000으로 들어간다고 적어 두었는데, 이 서버에서는 열리지 않습니다. OCI 보안 목록에 9000이 없고, 관리 화면을 암호화 없는 HTTP로 인터넷에 여는 것도 피하고 싶었습니다. 그래서 이미 쓰고 있는 SSH 연결에 터널을 하나 얹었습니다.
로컬 PC의 cmd에서 실행합니다.
ssh -i <개인키 경로> -L 9000:127.0.0.1:9000 ubuntu@<서버 IP>
-L 9000:127.0.0.1:9000은 로컬 PC의 9000번으로 들어온 연결을 SSH 안으로 실어 서버의 127.0.0.1:9000에 넘기라는 뜻입니다. 이 창을 켜 둔 채로 브라우저에서 http://localhost:9000을 열면 Portainer 화면이 나옵니다. 오가는 내용은 SSH가 암호화하므로 주소가 http여도 밖에서 들여다볼 수 없습니다.

첫 화면에서 관리자 계정을 만듭니다. Username(기본값 admin)과 Password를 넣고 Create user를 누르면 됩니다. 처음 메모에는 비밀번호를 8자리 이상이라고 적었는데, 지금 Portainer CE는 기본 최소 길이가 12자입니다. Get Started를 누르면 Home에 local 환경이 나오고, 여기서 컨테이너·볼륨·이미지 수와 CPU·메모리 사용량을 볼 수 있습니다.
Portainer는 처음 실행한 뒤 약 5분 안에 관리자 계정을 만들지 않으면 보안을 위해 설정 화면을 막습니다.
핵심 팁: 터널부터 열고 컨테이너를 띄웁니다
관리자 계정을 만들 수 있는 시간이 짧으므로, SSH 터널을 먼저 열어 둔 뒤 Portainer를 띄우면 여유 있게 계정을 만들 수 있습니다. 시간이 지나 화면이 막혔다면
docker restart portainer로 다시 시작하고 바로 접속하면 됩니다.
06 Compose로 Uptime Kuma 배포
Uptime Kuma는 웹사이트와 서버가 살아 있는지 주기적으로 확인하고 그래프로 보여 주는 오픈소스 모니터링 도구입니다. 이번에는 docker run 대신 설정을 파일로 남겨 두는 Docker Compose를 썼습니다. 서비스마다 폴더를 하나씩 두기로 했습니다.
mkdir -p ~/services/uptime-kuma
cd ~/services/uptime-kuma
nano docker-compose.yml
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
volumes:
- ./data:/app/data
ports:
- "127.0.0.1:3001:3001"
restart: always
처음 메모에는 맨 위에 version: '3.3' 줄이 있었는데, Compose v2는 이 줄을 쓰지 않고 남겨 두면 obsolete 경고를 띄워서 지웠습니다. ./data 볼륨에 모니터 설정과 기록이 저장되므로 컨테이너를 새로 만들어도 데이터는 남습니다.

Ctrl + O → Enter로 저장하고 Ctrl + X로 나온 뒤 백그라운드로 실행했습니다.
docker compose up -d
docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"

Container uptime-kuma 줄에 Started가 나오고, docker compose ps의 STATUS가 Up (healthy)로 바뀌는 것으로 확인했습니다.
07 루트 도메인 8080에 웹 컨테이너 붙이기
3편에서 루트 도메인은 127.0.0.1:8080으로 넘기도록 설정해 두었기 때문에, 8080을 채우지 않으면 계속 502가 납니다. 나중에 올릴 서비스가 정해지기 전까지 자리를 채울 겸, 같은 Compose 방식으로 정적 페이지 하나를 내보내는 nginx:alpine 컨테이너를 띄웠습니다.
mkdir -p ~/services/web/html
cd ~/services/web
echo '<h1>Hello from Oracle Cloud</h1>' > html/index.html
nano docker-compose.yml
services:
web:
image: nginx:alpine
container_name: web
volumes:
- ./html:/usr/share/nginx/html:ro
ports:
- "127.0.0.1:8080:80"
restart: always
docker compose up -d
curl -I http://127.0.0.1:8080
컨테이너 안의 Nginx는 자기 네트워크의 80번을 쓰기 때문에 서버의 Nginx(80·443)와 부딪치지 않고, 서버 쪽에서는 127.0.0.1:8080으로만 보입니다.

curl -I가 HTTP/1.1 200 OK를 돌려주는 것으로 확인했습니다. 이제 브라우저에서 https://<도메인>을 열면 3편의 502 대신 이 페이지가 나옵니다. 페이지를 바꾸고 싶으면 html/index.html만 고치면 되고, 다른 서비스를 루트 도메인에 올릴 때는 그 서비스의 포트를 127.0.0.1:8080으로 열면 됩니다.
08 status 서브도메인 A 레코드 추가
Uptime Kuma는 status.<도메인>이라는 서브도메인으로 열기로 했습니다. 처음 메모에는 이 단계가 빠져 있었는데, 이름이 서버 IP로 연결되지 않으면 Certbot의 도메인 확인이 실패합니다. 3편과 같은 Cloudflare 대시보드의 DNS → 레코드에서 레코드 추가를 눌렀습니다.
| 항목 | 값 |
|---|---|
| 형식 | A |
| 이름 | status |
| IPv4 주소 | 오라클 예약 퍼블릭 IP (3편과 같은 IP) |
| 프록시 상태 | DNS 전용(회색 구름) |
| TTL | 자동 |
저장한 뒤 로컬 cmd에서 nslookup status.<도메인>을 실행해 예약 IP가 나오는 것으로 확인했습니다.
주의: 레코드가 보인 뒤에 Certbot을 실행합니다
nslookup에 예약 IP가 나오기 전에 Certbot을 돌리면 도메인 확인이 실패하고, 실패가 쌓이면 Let’s Encrypt가 한동안 발급을 막습니다. 3편에서 본 것처럼 프록시 상태도 처음 발급 때는 반드시 DNS 전용으로 둡니다.
09 Nginx에 status 서브도메인 설정
3편과 같은 방식으로 서브도메인 전용 설정 파일을 만들었습니다.
sudo nano /etc/nginx/sites-available/status.<도메인>.conf
server {
listen 80;
server_name status.<도메인>;
location / {
proxy_pass http://127.0.0.1:3001;
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-Proto $scheme;
# WebSocket 지원 (실시간 모니터링용)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
3편의 설정과 다른 곳은 아래 세 줄입니다. Uptime Kuma 화면은 WebSocket으로 서버와 연결을 열어 둔 채 상태를 실시간으로 받는데, Nginx는 기본적으로 이 연결 전환(Upgrade) 요청을 뒤로 넘기지 않습니다. proxy_http_version 1.1과 Upgrade·Connection 헤더를 넣어야 대시보드가 끊기지 않고 갱신됩니다.

10 설정 활성화와 HTTPS 발급
링크를 만들고 문법 검사를 통과하면 바로 다시 읽게 한 뒤, 서브도메인 하나만 넣어 인증서를 받았습니다.
sudo ln -s /etc/nginx/sites-available/status.<도메인>.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d status.<도메인>
3편에서 Let’s Encrypt 계정 등록을 마쳤기 때문에 이번에는 이메일과 약관 질문 없이 바로 발급됐습니다.

새 인증서도 3편에서 확인한 certbot.timer가 함께 갱신하므로, 서브도메인을 늘릴 때마다 갱신 설정을 따로 할 필요는 없습니다. sudo certbot renew --dry-run을 다시 돌리면 루트 도메인과 status 두 인증서가 모두 성공으로 나옵니다.
11 Uptime Kuma 접속과 첫 모니터
브라우저에서 https://status.<도메인>을 열었습니다. 주소창 왼쪽의 사이트 정보 보기 아이콘을 누르면 연결이 안전함이 표시됩니다(3편에서 본 것처럼 Chrome은 초록색 자물쇠를 더는 쓰지 않습니다). 처음 열면 관리자 계정을 만드는 화면이 나옵니다.
주의: 첫 화면에서 바로 관리자 계정을 만듭니다
Uptime Kuma는 관리자 계정이 만들어지기 전까지 주소를 아는 누구나 첫 계정을 만들 수 있습니다. 서브도메인이 열리면 다른 일을 하기 전에 계정부터 만들어야 합니다.
계정을 만든 뒤 Add New Monitor로 첫 모니터를 추가했습니다.
| 항목 | 값 |
|---|---|
| Monitor Type | HTTP(s) |
| Friendly Name | 루트 도메인 |
| URL | https://<도메인> |
| Heartbeat Interval | 60초 |
07절에서 루트 도메인에 웹 컨테이너를 붙여 두었기 때문에, 저장하고 잠시 뒤 모니터가 초록색 Up으로 바뀌고 응답 시간 그래프가 그려지기 시작하는 것으로 확인했습니다. 8080을 비워 둔 채 이 모니터를 걸었다면 502 때문에 Down으로 나왔을 것입니다.
정리
- Ubuntu 24.04 ARM64에서는 apt의 docker.io와 docker-compose-v2로 Docker와 Compose v2를 설치했고, docker 그룹으로 sudo 없이 쓰게 했습니다. docker 그룹은 사실상 root 권한입니다.
- Docker가 -p로 연 포트는 UFW를 거치지 않으므로, 컨테이너 포트는 모두 127.0.0.1에만 열었습니다.
- Portainer는 9000을 인터넷에 열지 않고 SSH 터널로 접속했습니다. 관리자 계정은 띄운 뒤 몇 분 안에 만들어야 합니다.
- Compose 파일의 version 줄은 지웠고, 서브도메인은 A 레코드 → Nginx 설정(WebSocket 헤더 포함) → Certbot 순서로 HTTPS를 붙였습니다.
- 루트 도메인 8080에 웹 컨테이너를 붙여 3편의 502를 없앴고, Uptime Kuma로 그 도메인을 감시하기 시작했습니다.
마치며
이 글로 오라클 클라우드 무료 서버 시리즈를 마칩니다. 네 편 동안 만든 것은 다음과 같습니다.
| 편 | 한 일 |
|---|---|
| 1편 | 가입, PAYG 전환, Ampere A1 2 OCPU / 12GB, 부트 볼륨 200GB 인스턴스 생성 |
| 2편 | 임시 IP 구조 확인, UFW로 22·80·443만 허용, Swap 4GB, 한국 시간(KST) |
| 3편 | 예약 IP 전환, Cloudflare DNS, Nginx 리버스 프록시, Let’s Encrypt HTTPS와 자동 갱신 |
| 4편 | Docker·Compose, Portainer(SSH 터널), Uptime Kuma, 루트 도메인 웹 컨테이너, 서브도메인 HTTPS |
OCI 쪽 비용은 무료 한도 안에서 0원이고, 도메인 등록비만 따로 듭니다.
아직 해 보지 못한 일도 남아 있습니다. Let’s Encrypt의 90일 뒤 실제 자동 갱신은 아직 겪어 보지 않았고, 컨테이너 이미지를 언제 업데이트할지(docker compose pull 후 docker compose up -d)도 주기를 정하지 않았습니다. Uptime Kuma의 ./data와 Portainer의 portainer_data를 백업하는 방법도 아직 정하지 못했습니다. 이 서버 위에 서비스를 하나씩 올리면서 겪은 일은 따로 기록해 둘 생각입니다.
참고한 곳
- #오라클클라우드
- #OCI
- #무료서버
- #Docker
- #Portainer
연재 글 목록 · 오라클 클라우드 무료 서버 만들기
- 1편오라클 클라우드 무료 서버 구축 가이드: 가입부터 PAYG 전환, Ampere 인스턴스 생성까지
- 2편오라클 무료 서버 안정화, 임시 IP의 정체와 UFW·Swap·타임존 설정
- 3편오라클 클라우드 무료 서버에 내 도메인 연결하기: DNS A 레코드, Nginx 리버스 프록시, Let's Encrypt 무료 HTTPS
- 4편오라클 클라우드 무료 서버에 Docker 올리기: Portainer 관리, Uptime Kuma 배포와 서브도메인 HTTPS 연결 (지금 읽는 글)