study

Ubuntu 홈 서버 구축 - Docker Compose 기반 개발 환경 구성과 DB 보안

렁치 2026. 6. 28. 19:00

Ubuntu 홈 서버 구축 (2) - Docker Compose 기반 개발 환경 구성과 DB 보안

이번에는 Ubuntu 서버의 기본 보안 작업을 마무리했다.

Cloudflare에서 도메인을 관리하고, SSH 공개키 인증을 적용하고, Root 로그인과 비밀번호 로그인을 막고, UFW와 Fail2Ban, 자동 보안 업데이트까지 설정하면서 운영체제 자체를 먼저 단단하게 만드는 데 집중했다.

오늘은 그 위에 실제 개발 환경을 올리는 작업을 진행했다.

목표는 단순히 Docker를 설치하는 것이 아니라, 앞으로 게임 서버, 자동화 프로젝트, 데이터베이스 연구, 개인 실험 등을 모두 이 서버 위에서 운영할 수 있는 기반을 만드는 것이다.


오늘 목표

오늘 진행한 목표는 다음과 같았다.

  • Docker Compose 구조 정리
  • PostgreSQL 컨테이너 구축
  • SQL Server 컨테이너 구축
  • Portainer 구축
  • DB 포트를 외부에 공개하지 않는 구조 만들기
  • SSH Tunnel을 이용한 DB 접속
  • SSMS / pgAdmin 연결 테스트
  • VS Code Remote SSH만으로 DB 터널까지 자동 연결되도록 개선

전체 구조 먼저 생각해보기

오늘 작업하기 전에 먼저 서버에서 앞으로 어떤 역할을 맡길지 다시 정리했다.

Ubuntu Server
│
├── Docker Compose
│
├── PostgreSQL
│
├── SQL Server
│
├── Portainer
│
├── SurvivalGame
│
└── SkyzTrace

여기에 Nginx와 GitHub Actions가 추가되면 앞으로 대부분의 개인 프로젝트는 이 서버에서 운영하게 된다.

예전에는 서버를 하나 만들면 그냥 필요한 프로그램을 설치하는 정도로만 생각했는데, 이번에는 처음부터 구조를 잡아놓고 시작해보기로 했다.


Docker 디렉터리 구조 정리

우선 Docker 관련 폴더를 정리했다.

cd ~/docker

mkdir -p compose
mkdir -p nginx/conf.d
mkdir -p nginx/logs
mkdir -p game
mkdir -p skyztrace
mkdir -p postgres/data
mkdir -p mssql/data
mkdir -p backups

그리고 tree 명령으로 현재 구조를 확인했다.

sudo apt install tree -y
tree -L 3 ~/docker

최종적으로는 이런 형태가 되었다.

docker
├── backups
├── compose
├── game
├── mssql
│   └── data
├── nginx
│   ├── conf.d
│   └── logs
├── postgres
│   └── data
└── skyztrace

나중에는 game 폴더도 실제 프로젝트 이름인 survivalgame으로 변경할 예정이다.


Nginx 설치

오늘은 Reverse Proxy까지 진행하지는 않았지만, 다음 작업을 위해 미리 설치했다.

sudo apt install nginx -y

정상 동작 여부 확인

sudo systemctl status nginx

그리고

curl -I http://localhost

결과가

HTTP/1.1 200 OK

가 나오면서 정상적으로 실행되는 것을 확인했다.


Docker Compose를 하나로 통합

원래는 PostgreSQL만 따로 Compose를 사용하고 있었다.

그런데 앞으로

  • PostgreSQL
  • SQL Server
  • Portainer
  • 게임 서버
  • 자동화 프로젝트

까지 생각하면 Compose 파일이 여러 개 흩어져 있는 것은 관리하기 불편할 것 같았다.

그래서 기준점을 하나로 정했다.

~/docker/compose/docker-compose.yml

앞으로는 모든 서비스는 이 Compose 파일 하나에서 관리하기로 했다.


.env 분리

비밀번호나 계정 정보를 Compose 파일에 직접 적는 것도 마음에 들지 않았다.

그래서

~/docker/compose/.env

파일을 따로 만들었다.

예를 들면

TZ=Asia/Seoul

POSTGRES_USER=<POSTGRES_USER>
POSTGRES_PASSWORD=<POSTGRES_PASSWORD>

MSSQL_SA_PASSWORD=<MSSQL_SA_PASSWORD>

처럼 관리한다.

실제 블로그에는 당연히 계정명이나 비밀번호를 적지 않는다.

나중에 GitHub Actions를 붙일 때도 이런 구조가 훨씬 편할 것 같다.


PostgreSQL 구축

PostgreSQL은 Docker Compose에 추가했다.

처음에는 포트를 이렇게 열어두고 있었다.

ports:
  - "5432:5432"

이렇게 하면 호스트의 모든 인터페이스에 PostgreSQL이 열린다.

처음에는 별 생각이 없었는데, 곰곰이 생각해 보니 데이터베이스는 인터넷에 직접 노출할 이유가 거의 없었다.

그래서 다음처럼 수정했다.

ports:
  - "127.0.0.1:5432:5432"

이제 PostgreSQL은 서버 내부에서만 접근 가능하다.

확인 결과도

127.0.0.1:5432->5432/tcp

으로 정상적으로 변경되었다.


Portainer도 같은 방식으로 변경

Portainer도 처음에는

0.0.0.0:9443

처럼 외부에 열려 있었다.

Docker를 관리하는 웹 인터페이스인데 굳이 인터넷에 공개할 필요는 없다고 생각했다.

그래서 이것도

127.0.0.1

에만 바인딩했다.

결과적으로

127.0.0.1:9443
127.0.0.1:8000

만 사용하도록 변경했다.


SQL Server 추가

이번에는 SQL Server도 Docker Compose에 추가했다.

이미지는 SQL Server 2022 Developer Edition을 사용했다.

처음에는 latest 태그를 사용할까 고민했는데, 나중에 버전이 바뀌면서 예상하지 못한 문제가 생길 수도 있어서 특정 버전으로 고정했다.

Docker Compose에는 대략 이런 형태로 추가했다.

mssql:
    image: mcr.microsoft.com/mssql/server:2022-...

데이터도 볼륨으로 분리해서 컨테이너를 다시 만들어도 DB는 유지되도록 구성했다.


Docker Volume 때문에 생긴 문제

Compose를 하나로 통합하면서 오류가 하나 발생했다.

external volume not found

알고 보니 PostgreSQL 볼륨 이름이 내가 생각한 이름이 아니었다.

확인해 보니

docker volume ls

결과가

postgres_postgres_data

였다.

Compose에서 외부 볼륨 이름을 정확하게 지정해 주니까 정상적으로 해결됐다.

처음에는 왜 안 되는지 한참 헤맸는데, Compose 프로젝트 이름이 볼륨 이름 앞에 자동으로 붙는다는 걸 이번에 처음 알았다.


DB는 외부에 열지 않기로 했다

오늘 작업하면서 가장 많이 고민했던 부분이다.

처음에는

5432
1433

포트를 그냥 인터넷에 열면 되지 않을까 생각했었다.

하지만 DB는 웹 서버와 다르다.

외부 사용자가 직접 접속해야 하는 서비스가 아니라 관리자가 필요할 때만 접근하면 된다.

그래서 지금 구조는 이렇게 만들었다.

인터넷

×

DB 직접 접근 불가

↓

SSH

↓

Ubuntu

↓

Docker

↓

PostgreSQL
SQL Server

보안 측면에서도 훨씬 마음에 든다.


SSH Tunnel 사용

대신 관리자는 SSH 터널을 통해 접속한다.

예를 들어 SQL Server는

ssh -L 11433:127.0.0.1:1433 ubuntu

PostgreSQL은

ssh -L 15432:127.0.0.1:5432 ubuntu

이렇게 연결한다.

그러면 로컬에서는

localhost:11433
localhost:15432

로 접속할 수 있다.


처음에는 불편하다고 생각했다

솔직히 처음에는

"DB 사용할 때마다 터널 명령을 계속 입력해야 하나?"

라는 생각이 가장 먼저 들었다.

매번 PowerShell 열고

ssh -L ...

입력하는 건 생각보다 귀찮았다.

그래서 다른 방법이 없는지 찾아봤다.


SSH Config의 LocalForward

결국 해결 방법은 SSH Config에 있었다.

Windows의

C:\Users\<사용자>\.ssh\config

파일에

Host ubuntu
    HostName ubuntu.example.com
    User <USER>
    Port <SSH_PORT>

    LocalForward 11433 127.0.0.1:1433
    LocalForward 15432 127.0.0.1:5432
    LocalForward 19443 127.0.0.1:9443

를 추가했다.

여기에

ServerAliveInterval 60
ServerAliveCountMax 3

도 함께 넣었다.

이렇게 설정하니까 VS Code Remote SSH에서

ubuntu

로 접속하는 순간

  • SQL Server
  • PostgreSQL
  • Portainer

터널도 같이 생성된다.

결국 평소에는 VS Code로 서버만 접속하면 된다.

생각보다 훨씬 편하다.


로컬 포트를 다르게 사용한 이유

처음에는

1433
5432

그대로 사용하려고 했다.

그런데 내 PC에는 이미 SQL Server나 Oracle이 설치되어 있어서 포트 충돌 가능성이 있었다.

그래서 로컬 포트는 따로 사용하기로 했다.

서비스 로컬 포트 서버 포트
SQL Server 11433 1433
PostgreSQL 15432 5432
Portainer 19443 9443

이렇게 해두니 나중에 로컬 DB를 설치해도 서로 충돌하지 않는다.


SSMS와 pgAdmin 연결

SQL Server는 SSMS에서 연결했다.

127.0.0.1,11433

PostgreSQL은 pgAdmin에서 연결했다.

Host : 127.0.0.1

Port : 15432

처음에는 SQL Server 연결 오류 때문에 조금 헤맸는데, SSH 터널과 포트 설정을 다시 확인하면서 해결했다.

PostgreSQL도 간단한 테이블을 만들고 데이터를 넣어보면서 정상적으로 동작하는 것까지 확인했다.


현재 서버 구조

오늘 작업이 끝난 뒤 서버 구조는 다음과 같다.

Ubuntu Server

├── Docker Compose
│
├── PostgreSQL
│   └── localhost 전용
│
├── SQL Server
│   └── localhost 전용
│
├── Portainer
│   └── localhost 전용
│
├── Nginx
│
├── SSH
│
├── UFW
│
└── Fail2Ban

생각했던 개발 서버의 형태가 조금씩 갖춰지고 있는 것 같다.


오늘 느낀 점

오늘 가장 크게 느낀 건 서비스를 설치하는 것보다 어떻게 노출할 것인지가 더 중요하다는 점이었다.

Docker는 생각보다 금방 설치된다.

하지만

  • 어떤 포트를 열 것인지
  • 어떤 포트를 닫을 것인지
  • Compose를 어떻게 관리할 것인지
  • 볼륨을 어떻게 유지할 것인지

같은 부분은 처음에 잘 정리해 두는 게 훨씬 중요했다.

그리고 SSH Config의 LocalForward 기능은 오늘 가장 만족스러웠던 부분이다.

처음에는 DB를 사용할 때마다 터널을 새로 열어야 하는 줄 알았는데, 지금은 VS Code로 서버에 접속하는 것만으로 필요한 터널까지 함께 생성된다.

앞으로는 훨씬 편하게 작업할 수 있을 것 같다.


다음 작업

다음 목표는 실제 서비스를 올리는 것이다.

예정하고 있는 작업은 다음과 같다.

  • Nginx Reverse Proxy
  • HTTPS 적용
  • GitHub Actions 자동 배포
  • SurvivalGame 이전
  • SkyzTrace 이전

오늘까지는 개발 서버의 기반을 만드는 단계였다면, 다음부터는 실제 서비스를 운영하기 위한 환경을 하나씩 구축해 볼 예정이다.