Synology NAS에서 Django + MariaDB + Nginx Proxy Manager 구성 가이드
Title: Synology NAS에서 Django + MariaDB + Nginx Proxy Manager 구성 가이드
추천 Excerpt: Synology NAS에서 Docker로 Django/MariaDB를 구성하고 Nginx Proxy Manager로 도메인/SSL까지 운영 가능한 표준 배포 구성을 정리합니다.
추천 Tags: Synology, NAS, Docker, Django, MariaDB, Nginx Proxy Manager, SSL
1. 개요
Synology NAS에 Docker를 활용하여 Django, MariaDB, Nginx Proxy Manager를 연동하는 방법을 소개합니다.
이 구성은 웹 서비스 운영에 필요한 보안, 성능, 유지보수 를 모두 만족하며,
Ghost, WordPress 등 다양한 블로그/웹사이트에도 응용할 수 있습니다.
2. 프로젝트 폴더 구조
아래는 표준적인 Django 프로젝트의 폴더 트리 구조입니다.
/volume1/docker/django/
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
├── mariadb/ # MariaDB 데이터 저장 폴더
├── static/ # collectstatic 명령어로 수집된 정적 파일 위치
│ └── admin/ # Django 관리자 정적 파일
├── oopsproject/ # Django 프로젝트 루트
│ ├── manage.py # Django 진입점 스크립트
│ ├── app/ # Django 앱 폴더
│ │ ├── __init__.py
│ │ ├── urls.py # 앱 URLconf
│ │ ├── views.py # 앱 뷰 함수
│ │ ├── templates/ # 앱 템플릿 폴더
│ │ │ └── home.html # 메인 페이지 템플릿
│ │ └── static/ # 앱 정적 파일 (CSS, JS, 이미지 등)
│ │ └── app/
│ │ ├── css/
│ │ │ └── home.css
│ │ ├── js/
│ │ │ └── home.js
│ │ └── img/
│ │ └── favicon.ico
│ └── oopsproject/ # Django 프로젝트 설정 폴더
│ ├── __init__.py
│ ├── settings.py # Django 설정 파일
│ ├── urls.py # 메인 URLconf
│ └── wsgi.py # WSGI 진입점3. 각 파일의 전체 코드
3-1. manage.py
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oopsproject.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
3-2. app/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name='home'),
]
3-3. app/views.py
from django.shortcuts import render
def home(request):
return render(request, 'home.html')
3-4. app/templates/home.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>My Django Info</title>
<link rel="icon" href="{% static 'app/img/favicon.ico' %}">
<link rel="stylesheet" href="{% static 'app/css/home.css' %}">
</head>
<body>
<div class="card">
<div class="card-title">Project Info</div>
<div class="info-label">현재 시간</div>
<div class="info-value" id="current-time"></div>
<div class="info-label">오늘 날짜</div>
<div class="info-value" id="current-date"></div>
<div class="info-label">올해의 주차</div>
<div class="info-value"><span id="week-number"></span>번째 주</div>
</div>
<script src="{% static 'app/js/home.js' %}"></script>
</body>
</html>
3-5. app/static/app/css/home.css
body {
background: #181a1b;
font-family: 'Segoe UI', Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.card {
background: #23272f;
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0,0,0,0.45);
padding: 32px 40px;
min-width: 320px;
text-align: center;
}
.card-title {
font-size: 1.3rem;
font-weight: bold;
color: #e0e7ef;
margin-bottom: 20px;
letter-spacing: 0.02em;
}
.info-label {
font-size: 1.05rem;
color: #a1a1aa;
margin-top: 12px;
margin-bottom: 4px;
}
.info-value {
font-size: 1.6rem;
color: #60a5fa;
font-weight: 500;
}
3-6. app/static/app/js/home.js
function getWeekNumber(d) {
d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
const yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
return Math.ceil(((d - yearStart) / 86400000 + 1)/7);
}
function updateDateTime() {
const now = new Date();
document.getElementById('current-time').textContent =
now.toLocaleTimeString('ko-KR', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
document.getElementById('current-date').textContent =
now.toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'short'
});
document.getElementById('week-number').textContent =
getWeekNumber(now);
}
setInterval(updateDateTime, 1000);
window.onload = updateDateTime;
3-7. app/static/app/img/favicon.ico
- favicon.ico 파일 을 해당 위치에 복사 (온라인에서 생성하거나, 직접 준비)
3-8. oopsproject/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('app.urls')),
]
3-9. oopsproject/settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'your-insecure-secret-key')
DEBUG = False
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
ALLOWED_HOSTS = [
'192.168.1.100', # 예시 NAS 내부 IP
'yourdomain.com', # 예시 도메인
'localhost',
'127.0.0.1'
]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'oopsproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'app/templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'oopsproject.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'exampledb',
'USER': 'exampleuser',
'PASSWORD': 'examplepassword',
'HOST': 'db',
'PORT': '3306',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
'use_unicode': True,
},
'CONN_MAX_AGE': 300
}
}
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
LANGUAGE_CODE = 'ko-kr'
TIME_ZONE = 'Asia/Seoul'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = '/static/'
STATIC_ROOT = '/app/static'
STATICFILES_DIRS = []
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
MEDIA_URL = '/media/'
MEDIA_ROOT = '/app/media'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG' if DEBUG else 'INFO',
'class': 'logging.FileHandler',
'filename': '/var/log/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG' if DEBUG else 'INFO',
'propagate': True,
},
},
}
3-10. oopsproject/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oopsproject.settings')
application = get_wsgi_application()
4. 셋팅 방법
4-1. 파일 구조 준비
- **프로젝트 루트(
/volume1/docker/django/)**에 폴더 트리 구조를 만듭니다. - 각 파일을 위 코드대로 작성 하여 배치합니다.
- favicon.ico 는 온라인에서 생성하거나 직접 준비하여
app/static/app/img/에 복사합니다.
4-2. Docker 및 MariaDB, Nginx Proxy Manager 설치
- Docker, docker-compose 를 Synology NAS에 설치합니다.
- MariaDB, Django, Nginx Proxy Manager 컨테이너를 위한
docker-compose.yml을 작성합니다.
version: '3.8'
services:
web:
build: .
ports:
- "55079:55079"
volumes:
- ./:/app
- /volume1/docker/django/static:/app/static
environment:
DJANGO_SETTINGS_MODULE: oopsproject.settings
depends_on:
- db
restart: unless-stopped
networks:
- django_network
db:
image: mariadb:11.3
volumes:
- type: bind
source: /volume1/docker/django/mariadb
target: /var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: "example_root_password"
MYSQL_DATABASE: "exampledb"
MYSQL_USER: "exampleuser"
MYSQL_PASSWORD: "examplepassword"
MYSQL_ROOT_HOST: "%"
TZ: "Asia/Seoul"
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --wait_timeout=28800
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- django_network
restart: unless-stopped
networks:
django_network:
driver: bridge
- Nginx Proxy Manager 컨테이너도 별도로 실행하여,
https://yourdomain.com도메인과 SSL 인증서를 적용합니다.
4-3. Django 서버 실행 및 초기화
- 컨테이너 실행
docker-compose up -d
- 마이그레이션 적용
docker-compose exec web python oopsproject/manage.py migrate
- 정적 파일 수집
docker-compose exec web python oopsproject/manage.py collectstatic --noinput
- 슈퍼유저(관리자) 계정 생성
docker-compose exec web python oopsproject/manage.py createsuperuser
4-4. Nginx Proxy Manager 설정
- Nginx Proxy Manager 웹 UI 접속
- Proxy Host 추가
- Domain Names:
yourdomain.com - Scheme:
http - Forward Hostname/IP:
192.168.1.100(또는 NAS IP) - Forward Port:
55079(Django 포트)
- Domain Names:
- SSL 인증서 발급
- Request a new SSL Certificate 선택
- Domain Names:
yourdomain.com - Force SSL: 활성화
5. 결과 확인
https://yourdomain.com접속 시 메인 페이지(카드형 UI) 확인https://yourdomain.com/secret-admin/접속 후 관리자 로그인 가능- favicon.ico 가 브라우저 탭에 정상 표시
6. 추가 팁
- 환경변수 분리: SECRET_KEY, DB_PASSWORD 등은 환경변수로 관리
- 에러 페이지: 404.html, 500.html 템플릿 추가 권장
- 백업: 데이터베이스 및 소스 코드 주기적 백업