Skip to content

devspoons/devspoon-web

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

217 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

devspoon-web

This open source project offer docker that three kind of web or API service solutions by php, gunicorn, uwsgi based on nginx server. You can easily create custom configuration files for nginx using a shell script. Supports https and certbot auto-extension script. there are default security settings in the nginx config file. docker-compose allows you to easily install and operate multiple domain servers on one server. For server caches, docker-compose supports installing and connecting redis and redis-state. Anyone can install web services easily using docker and docker-compose. Af you want to use python and php service at same time, this solution can help you better.

introduce "Devspoon-Projects"

  • We provide an open source infrastructure integration solution that can easily service Python, Django, PHP, etc. using docker-compose. You can install the commercial-level customizable nginx service and redis at once, and install and manage more services at once. If you are interested, please visit Devspoon-Projects.

Official guide document

  • preparing...

Features

  • Support to make configuration files for each service(conf, certbot) : You can use a shell script to generate conf files for https and proxy settings in nginx. Supports a script to restart docker using crontab to complete certbot authentication of the docker container.

  • Efficiently dockerfile configuration for development and service operation : The log folder is interlocked by "volumes" in docker-compose.yml so that user can can be tracked problems even when the docker container is stopped. Webroot, nginx config, etc. are frequently modified during development so these are interlocked by "volumes"

  • Provide reverse proxy function : Multiple web and app services can be provided through one nginx with php or python and services can be provided simultaneously. A shell script is provided to easily create a proxy config file so that it can be integrated with the web UI of other services.

  • Provides easy distributed service operation method : You can use multiple web servers through proxy, and you can use multiple app servers on one web server.

  • Easy service changes using Docker-compose : In docker-compose, various configuration items are defined and commented out. By deleting comments or adjusting your desired settings, you can easily create an environment that suits your purposes.

  • log file collection : Log files for all services are stored in log/ and can be monitored even after container termination.

  • redis and ssl : Information such as configuration files, data, and keys for Redis and SSL are attached as volumes to the redis and ssl folders in docker-compose, so they can be reused when the container is terminated and restarted.

  • Ready-to-run sample apps : Django (django_sample), FastAPI (fastapi_sample), Flask (flask_sample), and PHP (php_sample) live under www/ so each of the six stacks (gunicorn / uvicorn / uwsgi / daphne / php-7.3 / php-8.4) can be brought up immediately after git clone. Samples are domain-agnostic โ€” bind to localhost first, swap to your domain when ready.

  • Worker privilege drop (www-data) : gunicorn / uvicorn / uwsgi / php-fpm workers all run as www-data (uid 33) โ€” the container boots as root (for uv sync etc.) but workers are dropped to least privilege. Each compose command runs chown -R www-data:www-data /www/${PROJECT_DIR} before app startup so SQLite/media writes succeed under the dropped UID. uwsgi master uses uid/gid = www-data; gunicorn arbiter stays root and forks workers via setuid.

  • Secret separation (.env-example) : Every stack ships a tracked .env-example (compose/web-service/nginx_*/.env-example), while the actual .env is gitignored. Copy โ†’ fill in Redis password / Flower credentials / etc., never commit the live file. ${VAR:?} checks in the compose files fail-fast if a required secret is missing.

  • Bot blocker auto-update + supply-chain hardening : Integrates nginx-ultimate-bad-bot-blocker. Container cron refreshes the blocklist every 6 hours. The installer scripts themselves are pinned to a fixed commit SHA and verified with sha256 at build time โ€” no master floating reference.

  • Dynamic gzip compression : All four nginx config directories (gunicorn / uvicorn / uwsgi / php; daphne reuses gunicorn's) enable gzip on with level 5, gzip_min_length 1024, and gzip_proxied any for JSON/HTML/CSS/JS/XML/SVG payloads. Proxy-passed backend responses are compressed too. Pre-compressed .gz static assets are served via gzip_static on.

Considerations

  • No DB service : This open source does not provide DB as docker to suggest stable operation. It is recommended to install it on a real server and access it using a network, such as port 3306. We hope that this will be done for distributed services as well. We hope that this will be consider for distributed services as well.

  • Development-oriented docker service : This open source is designed for focused on development-oriented rather than perfect docker container distribution and is suitable for startups or new service development teams with frequent initial modifications and tests.

  • Considering on-premise servers : This solution is built for on-premises servers. However, since it is currently being used as a test and commercial service in OCI (Oracle Cloud Infrastructure), it can be used in environments such as AWS and GCP without problems.

Operations Guide

์šด์˜ ๊ฐ€์ด๋“œ๋Š” docs/operations-guide/nginx-hardening/ ์•„๋ž˜์— ์‹œ๋ฆฌ์ฆˆ๋กœ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ์‹œ์ž‘์ ์€ OPS-GUIDE-001 Master Index โ€” ์œ„ํ˜‘ ๋ชจ๋ธ, ์šฐ์„ ์ˆœ์œ„ ๋งคํŠธ๋ฆญ์Šค, ๋ถ„๊ธฐ๋ณ„ ๋กœ๋“œ๋งต, ์‹œ๋ฆฌ์ฆˆ ์ธ๋ฑ์Šค๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๊ฐ sub-guide ๋Š” ๋„๋ฉ”์ธ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ๋˜์–ด ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค:

๋ฒˆํ˜ธ ๋ฌธ์„œ ๋‹ค๋ฃจ๋Š” ์˜์—ญ
OPS-GUIDE-001 Master Index ์œ„ํ˜‘ ๋ชจ๋ธ, ์šฐ์„ ์ˆœ์œ„ ๋งคํŠธ๋ฆญ์Šค, ๋กœ๋“œ๋งต, ๊ณตํ†ต ๋กค๋ฐฑ, ์‹œ๋ฆฌ์ฆˆ ์ธ๋ฑ์Šค, review/update ์ •์ฑ…
OPS-GUIDE-002 TLS / ์ธ์ฆ์„œ ์šด์˜ ์ธ์ฆ์„œ ๋งŒ๋ฃŒ ๋ชจ๋‹ˆํ„ฐ๋ง, HSTS preload, Let's Encrypt ๊ณ„์ • ๋ฐฑ์—…
OPS-GUIDE-003 ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ณ„์ธต ๋ฐฉ์–ด WAF (ModSecurity + OWASP CRS), fail2ban, CSP ๋‹จ๊ณ„์  ๋„์ž…
OPS-GUIDE-004 ์ปจํ…Œ์ด๋„ˆ / ์ด๋ฏธ์ง€ ๋ณด์•ˆ ๋ฆฌ์†Œ์Šค ์ œํ•œ, read-only filesystem, ์ด๋ฏธ์ง€ ์ทจ์•ฝ์  ์Šค์บ๋‹, SBOM/์„œ๋ช…, egress ํ•„ํ„ฐ๋ง, ๋ฐฑ์—”๋“œ ๊ฒฉ๋ฆฌ
OPS-GUIDE-005 ์šด์˜ ๊ฐ€์‹œ์„ฑ / ๋กœ๊ทธ / ๋ฉ”ํŠธ๋ฆญ ๋กœ๊ทธ ํšŒ์ „, Observability ์Šคํƒ, ์ปค์Šคํ…€ ์—๋Ÿฌ ํŽ˜์ด์ง€, ๊ฐ์‚ฌ ๋กœ๊ทธ immutability, secrets ๊ด€๋ฆฌ, ๋ฐฑ์—…/DR
OPS-GUIDE-006 ์—ฃ์ง€ / ๋„คํŠธ์›Œํฌ HTTP/3, real_ip, SSL mount ๋ฒ”์œ„, Slowloris, CONTINUATION flood, DDoS playbook

๊ฐ ๋ฌธ์„œ๋Š” ๊ทผ๊ฑฐ(Why) โ†’ ํ˜„์žฌ ์ƒํƒœ โ†’ ๊ตฌํ˜„ ๋‹จ๊ณ„ + ์„ค์ • ์Šค๋‹ˆํŽซ โ†’ ๊ฒ€์ฆ ๋ฐฉ๋ฒ• โ†’ ๋ชจ๋‹ˆํ„ฐ๋ง โ†’ ๋กค๋ฐฑ โ†’ ํ”ํžˆ ๋น ์ง€๋Š” ํ•จ์ • ์˜ 7๊ฐœ ์ ˆ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. PR ์€ ์‹œ๋ฆฌ์ฆˆ ID ๋ฅผ ์ œ๋ชฉ์— ๋ช…์‹œ (OPS-GUIDE-003: WAF Phase 2 exclusion ์ถ”๊ฐ€) ํ•˜์—ฌ ๋ถ„๊ธฐ review ๊ฐ€ ์ถ”์  ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

Install & Run

  1. Make webroot folder

    User have to make new folder under www path
    
    Example : /www/home_test
    
  2. Make a conf file of nginx

    • PHP service (PHP 7.3 / 8.4 dual-version)

      The PHP stack ships in two parallel versions selectable per deployment:

      • PHP 7.3 (legacy) โ€” romeoz/docker-phpfpm:7.3 base, Debian multi-version paths (/etc/php/7.3/fpm/...)
      • PHP 8.4 (current) โ€” official php:8.4-fpm-bookworm base, single-path layout (/usr/local/etc/php{,-fpm.d}/...)

      Each version has its own Dockerfile, compose stack, and PHP config folder (php.ini patched for PHP 8.x removals). The nginx config folder is shared because nothing in it depends on the PHP version. Both stacks bind ports 80/443, so they cannot run simultaneously โ€” pick one per host.

      • PHP service installation [nginx for php] (shared between 7.3 and 8.4)

        In config/web-server/nginx/php
        There are 2 shell scripts (nginx_http_conf.sh, nginx_https_conf.sh)
          - nginx_http_conf.sh  โ†’ sample_nginx_http.conf  โ†’ conf.d/<name>_php_ng_http.conf
          - nginx_https_conf.sh โ†’ sample_nginx_https.conf โ†’ conf.d/<name>_php_https_ng.conf
        Use "chmod +x xxxx.sh" command, you activate shell script and run. then it make conf file
        nginx's a conf file will be in conf.d folder. HTTP output always ends with "_http".
        if your webroot path has sub-level, input type must be following as "\\/www\\/shop\\/shop_kings
        
        Shell script required informations like bellow
        webroot : ex -> shop_kings
        domain : ex -> xxxx.com
        portnumber : ex -> 80
        appname : ex -> php-app-7.3   (for the PHP 7.3 stack)
                   or  php-app-8.4   (for the PHP 8.4 stack)
                   โ†’ must match container_name in compose/web-service/nginx_php-<ver>/docker-compose.yml
        serviceport : ex -> 9000 (php-fpm listen port; same in both stacks)
        filename : ex -> xxxx (it's the name for nginx's conf file)
        
      • PHP service installation [php application] (version-specific folder)

        PHP 7.3 โ†’ config/app-server/php-7.3
        PHP 8.4 โ†’ config/app-server/php-8.4
                  (php.ini is patched for PHP 8.x removals: track_errors, sql.safe_mode,
                   session.hash_*, [Interbase], [mcrypt] sections removed; error_reporting
                   cleaned of ~E_STRICT; session.use_only_cookies / use_trans_sid /
                   referer_check commented out. Each patch is annotated inline with a
                   "; [PHP 8.4] ..." comment in the file.)
        
        Each folder contains 1 shell script (php_conf.sh) that generates the pool config.
        Use "chmod +x xxxx.sh" command to activate, then run. It writes to pool.d/.
        
      • Run docker-compose.yml (pick one version)

        PHP 7.3 โ†’  cd compose/web-service/nginx_php-7.3
        PHP 8.4 โ†’  cd compose/web-service/nginx_php-8.4
        
        Execute docker-compose.yml using "docker compose up -d" command.
        Before first start, copy .env-example to .env and fill in REDIS_PASSWORD
        (placeholder REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD must be replaced).
        redis is gated by "profiles: redis" in PHP stacks โ€” start it via:
            docker compose --profile redis up -d
        
        Cannot run both stacks at once: both bind host ports 80/443.
        To switch versions: "docker compose stop" in the running stack first, then "up -d" in the other.
        

        PHP .env-example ์˜ ๋ณ€์ˆ˜ ์…‹์ด Python stack ๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. PHP ์Šคํƒ์€ ๋‹จ์ˆœํ™”๋œ ๋ณ€์ˆ˜ (LOG_DRIVER, LOG_OPT_MAXF, LOG_OPT_MAXS, REDIS_PASSWORD, ULIMIT_NOFILE_SOFT/HARD) ๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Python stack ์˜ PROJECT_DIR / WORKERS / PROJECT_NAME / FLOWER_* / GUNICORN_PORT ๋Š” PHP ์—์„œ๋Š” ์ •์˜๋˜์ง€ ์•Š์œผ๋ฉฐ, PROJECT_DIR ๋Œ€์‹  nginx sample conf ์˜ root /www/php_sample ๊ฒฝ๋กœ๊ฐ€ ์ง์ ‘ ๊ฐ€๋ฆฌํ‚ค๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ(www/php_sample/) ๊ฐ€ ๊ณ ์ • webroot ์ž…๋‹ˆ๋‹ค. ์ƒˆ php ํ”„๋กœ์ ํŠธ๋กœ ๊ต์ฒดํ•˜๋ ค๋ฉด www/<myphp>/ ๋ฅผ ๋งŒ๋“  ๋’ค nginx conf ์˜ root /www/<myphp> ๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค (compose ๋ณ€์ˆ˜ ๋ณ€๊ฒฝ ๋ถˆํ•„์š”).

    • Gunicorn service

      • Gunicorn service installation [nginx for gunicorn]

        In config/web-server/gunicorn
        There are 2 shell script
        Use "chmod +x xxxx.sh" command, you activate shell script and run.sh then it make conf file
        nginx's a conf file will be in conf.d folder
        * if your webroot path has sub-level, input type must be following as "\\/www\\/shop\\/shop_kings
        
        Shell script required informations like bellow
        webroot : ex -> shop_kings
        domain : ex -> xxxx.com
        portnumber : ex -> 80
        appname : ex -> gunicorn-app (user must be use "container name" referenced in docker-compose.yml file)
        serviceport : ex -> 8000 (gunicorn application service port)
        filename : ex -> xxxx (it's the name for nginx's conf file)
        
      • Gunicorn service installation [gunicorn application]

        In config/app-server/gunicorn/
          - gunicorn.conf.py  โ†’ Gunicorn ์„ค์ • (workers, bind, user="www-data", group="www-data" ๋“ฑ)
        
        run.sh / make_run.sh ํŒจํ„ด์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ docker-compose.yml ์˜
        gunicorn-app service ๊ฐ€ ์ง์ ‘:
        
          command: bash -c "chown -R www-data:www-data /www/${PROJECT_DIR} \
                   && uv sync --inexact --extra gunicorn --extra celery \
                   && exec gunicorn -c /gunicorn/gunicorn.conf.py"
        
        ์œ„ ํ•œ ์ค„๋กœ
          (1) /www/${PROJECT_DIR} ์†Œ์œ ๊ถŒ์„ www-data ๋กœ ๊ฐ•ํ•˜ (์›Œ์ปค ๊ถŒํ•œ ๊ฐ•ํ•˜, ยง0.5)
          (2) uv ๊ฐ€ pyproject.toml ์˜ [gunicorn,celery] extras ๋ฅผ ์ปจํ…Œ์ด๋„ˆ site-packages
              ์— ๋™๊ธฐํ™” (venv ๋ฏธ์‚ฌ์šฉ ์ •์ฑ… ยง8)
          (3) gunicorn ์„ ์‚ฌ์ „ ์ž‘์„ฑ๋œ /gunicorn/gunicorn.conf.py ๋กœ ๊ธฐ๋™
        ์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ณ„๋„ run.sh ์ž‘์„ฑ ๋ถˆํ•„์š”.
        
        ์ƒˆ ํ”„๋กœ์ ํŠธ๋กœ ๊ต์ฒดํ•˜๋ ค๋ฉด .env ์˜ PROJECT_DIR ๋งŒ ๋ฐ”๊พธ์„ธ์š”.
        
      • Run docker-compose.yml

        Get move to compose/web-service/nginx_gunicorn
        Before first start, copy .env-example to .env and fill in REDIS_PASSWORD,
        FLOWER_ID, FLOWER_PWD. CELERY_BROKER_URL is no longer stored in .env โ€”
        it is composed from REDIS_PASSWORD at compose time (SSOT, see ยง0.5.3).
        
        Run docker-compose.yml using "docker compose up -d".
        For celery / celery-beat / flower: "docker compose --profile celery up -d".
        (redis-stats has been removed โ€” see ยง0.5.7)
        
    • UWSGI service

      • UWSGI service installation [nginx for uwsgi]

        In config/web-server/uwsgi
        There are 2 shell script
        Use "chmod +x xxxx.sh" command, you activate shell script and run.sh then it make conf file
        nginx's a conf file will be in conf.d folder
        * if your webroot path has sub-level, input type must be following as "\\/www\\/shop\\/shop_kings
        
        Shell script required informations like bellow
        webroot : ex -> shop_kings
        domain : ex -> xxxx.com
        portnumber : ex -> 80
        appname : ex -> uwsgi-app (user must be use "container name" referenced in docker-compose.yml file)
        serviceport : ex -> 8000 (uwsgi application service port)
        filename : ex -> xxxx (it's the name for nginx's conf file)
        
      • UWSGI service installation [uwsgi application]

        In config/app-server/uwsgi
          - uwsgi_conf.sh  โ†’ uwsgi.ini ์ƒ์„ฑ๊ธฐ (๋„๋ฉ”์ธ / chdir / module ๋“ฑ ์ž…๋ ฅ)
          - uwsgi.ini      โ†’ ์ƒ์„ฑ๊ธฐ ์‚ฐ์ถœ๋ฌผ (master uid=33 gid=33 ๊ถŒํ•œ ๊ฐ•ํ•˜ ํฌํ•จ)
        
        run.sh / make_run.sh ํŒจํ„ด์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ docker-compose.yml ์˜
        uwsgi-app service ๊ฐ€ ์ง์ ‘:
        
          command: bash -c "chown -R www-data:www-data /www/${PROJECT_DIR} \
                   && uv sync --inexact --extra uwsgi --extra celery \
                   && exec uwsgi --ini /uwsgi/uwsgi.ini"
        
        ์œ„ ํ•œ ์ค„๋กœ (1) /www ์†Œ์œ ๊ถŒ ๊ฐ•ํ•˜ (2) uv ์˜ [uwsgi,celery] extras ๋™๊ธฐํ™”
        (3) uwsgi master ๊ธฐ๋™ ์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ณ„๋„ run.sh ์ž‘์„ฑ ๋ถˆํ•„์š”.
        
        ์ƒˆ ํ”„๋กœ์ ํŠธ๋กœ ๊ต์ฒดํ•˜๋ ค๋ฉด .env ์˜ PROJECT_DIR ๋งŒ ๋ฐ”๊พธ์„ธ์š”.
        
      • Run docker-compose.yml

        Get move to compose/web-service/nginx_uwsgi
        Before first start, copy .env-example to .env and fill in REDIS_PASSWORD,
        FLOWER_ID, FLOWER_PWD. CELERY_BROKER_URL is no longer in .env (see ยง0.5.3).
        
        Execute docker-compose.yml using "docker compose up -d".
        For celery / celery-beat / flower: "docker compose --profile celery up -d".
        (redis-stats has been removed โ€” see ยง0.5.7)
        
    • Uvicorn (ASGI) service

      ยง0.5.9 ๋™๊ธฐํ™”์—์„œ config/web-server/nginx/uvicorn/ (์ „์ฒด) ์™€ config/app-server/uvicorn/gunicorn_uvicorn.conf.py ๊ฐ€ ์‹ ์„ค๋˜์–ด ๋ณธ ์Šคํƒ์„ nginx ๋‹จ์—์„œ ์ •์ƒ generate / ์šด์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ „ ๋ฒ„์ „์—๋Š” compose/.../nginx_uvicorn/ ์Šคํƒ๋งŒ ์กด์žฌํ•˜๊ณ  nginx conf ํด๋”๊ฐ€ ์—†์–ด, ๋„๋ฉ”์ธ conf ๋ฅผ ๋งŒ๋“ค ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

      • Uvicorn service installation [nginx for uvicorn]

        In config/web-server/nginx/uvicorn
        There are 2 shell scripts (nginx_http_conf.sh, nginx_https_conf.sh)
          - nginx_http_conf.sh  โ†’ sample_nginx_http.conf  โ†’ conf.d/<name>_uvicorn_ng_http.conf
          - nginx_https_conf.sh โ†’ sample_nginx_https.conf โ†’ conf.d/<name>_uvicorn_ng_https.conf
        Use "chmod +x xxxx.sh" command, you activate shell script and run.
        
        Shell script required informations like bellow
        webroot : ex -> fastapi_sample           (or django_sample for ASGI Django)
        domain : ex -> xxxx.com
        portnumber : ex -> 80
        appname : ex -> uvicorn-app
                    โ†’ must match container_name in compose/web-service/nginx_uvicorn/docker-compose.yml
        serviceport : ex -> 8000
        filename : ex -> xxxx (it's the name for nginx's conf file)
        
      • Uvicorn service installation [uvicorn application]

        In config/app-server/uvicorn there are TWO config files:
          - uvicorn.conf.py           โ†’ ํ‘œ์ค€ uvicorn ์ง์ ‘ ๊ธฐ๋™ ์‹œ ์‚ฌ์šฉ
          - gunicorn_uvicorn.conf.py  โ†’ gunicorn + UvicornWorker ํŒจํ„ด (๋‹ค์ˆ˜ ์›Œ์ปค prefork ASGI)
        Both load via UV_PROJECT_ENVIRONMENT=/usr/local (venv ๋ฏธ์‚ฌ์šฉ ์ •์ฑ…, ยง8).
        
      • Run docker-compose.yml

        Get move to compose/web-service/nginx_uvicorn
        Before first start, copy .env-example to .env and fill in REDIS_PASSWORD,
        FLOWER_ID, FLOWER_PWD. CELERY_BROKER_URL is composed from REDIS_PASSWORD (ยง0.5.3).
        
        Execute docker-compose.yml using "docker compose up -d".
        For celery / celery-beat / flower: "docker compose --profile celery up -d".
        
    • Daphne (ASGI WebSocket) service โ€” devspoon ๊ณ ์œ  ์Šคํƒ (aisum-infrakit ์—๋Š” ์—†์Œ)

      Stack: compose/web-service/nginx_daphne/
      Logrotate dropins: script/logrotate/daphne/{daphne,celery/daphne-celery,celerybeat/daphne-celerybeat}
      Image: shares devspoon-py-app:latest (gunicorn / uvicorn ๊ณผ ๋™์ผ ๋ฒ ์ด์Šค, ยง0.5.4)
      Use case: Django Channels ๊ฐ™์€ WebSocket-only ์š”๊ตฌ์‚ฌํ•ญ. nginx conf ๋Š” gunicorn ์Šคํƒ์˜
      sample ์„ base ๋กœ location ๋ณ„ ws_pass ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉ.
      

www ์ƒ˜ํ”Œ ์•ฑ (www/)

www/ ์•„๋ž˜์˜ ์ƒ˜ํ”Œ ์•ฑ์€ ๊ฐ ์Šคํƒ์„ ์ฆ‰์‹œ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•œ reference ์ž…๋‹ˆ๋‹ค. ์šด์˜ ์ฝ”๋“œ๋Š” ๊ฐ™์€ ์œ„์น˜์— ์ž์‹ ์˜ ํด๋”๋กœ ๋‘๋ฉด ๋ฉ๋‹ˆ๋‹ค.

ํด๋” ๋ฐฑ์—”๋“œ / ์Šคํƒ ๋น„๊ณ 
www/django_sample/ gunicorn / uwsgi / daphne / uvicorn ๋ชจ๋‘์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ. Django 4.0.6 + uv-managed (pyproject.toml, uv.lock) .python-version = 3.14. ํ˜ธ์ŠคํŠธ์—์„  uv sync ๊ฐ€ .venv ์ž๋™ ์ƒ์„ฑ, ์ปจํ…Œ์ด๋„ˆ์—์„  ์‹œ์Šคํ…œ site-packages ์ง์„ค์น˜ (ยง8)
www/fastapi_sample/ uvicorn ์ „์šฉ. FastAPI ์ตœ์‹ , uv-managed ยง0.5.9 ๋„์ž… โ€” uvicorn ์Šคํƒ์˜ ๋™์ž‘ ๊ฒ€์ฆ์šฉ
www/flask_sample/ gunicorn ๋˜๋Š” uwsgi ์ „์šฉ (WSGI). Flask, uv-managed ยง0.5.9 ๋„์ž… โ€” WSGI ์Šคํƒ์˜ ๋น„-Django ๊ฒ€์ฆ์šฉ
www/php_sample/ php-fpm (7.3 ๋˜๋Š” 8.4) ์šฉ. ๋‹จ์ผ index.php ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ๋งˆ์šดํŠธ /var/www/html
www/certbot/ ์ปจํ…Œ์ด๋„ˆ ์•ˆ ACME webroot ํ‘œ์ค€ ์œ„์น˜ .gitkeep ๋งŒ ๋‘์–ด ๋นˆ ๋””๋ ‰ํ† ๋ฆฌ ์ถ”์ . ๋„๋ฉ”์ธ ๋ฐœ๊ธ‰ ์‹œ nginx conf ์˜ /.well-known/acme-challenge/ ๊ฐ€ ์ด ๊ฒฝ๋กœ๋กœ alias

๊ธฐ์กด ์‚ฌ์šฉ ํ๋ฆ„์€ ๊ทธ๋Œ€๋กœ:

1) ์ƒˆ ์•ฑ: www/myapp/  ๋ฅผ ๋งŒ๋“ ๋‹ค.
2) config/web-server/nginx/<stack>/nginx_http_conf.sh -w myapp -d ... ๋กœ ๋„๋ฉ”์ธ conf ์ƒ์„ฑ.
3) compose/web-service/nginx_<stack>/.env ์˜ PROJECT_DIR=myapp ์œผ๋กœ ์„ค์ •.
   (docker-compose.yml ์€ ${PROJECT_DIR} ๋ณ€์ˆ˜ ์น˜ํ™˜๋งŒ ํ•˜๋ฉฐ, ๊ฐ’ ์ž์ฒด๋Š” .env ๊ฐ€ ๋ณด์œ )
4) docker compose up -d --build

conf.d/ ์˜ ์˜๊ตฌ sample .conf ์ •์ฑ… (ํ…Œ์ŠคํŠธ์šฉ ์ฆ‰์‹œ ๊ฒ€์ฆ)

๊ฐ ์Šคํƒ์˜ config/web-server/nginx/<stack>/conf.d/ ๋””๋ ‰ํ„ฐ๋ฆฌ์—๋Š” 2 ์ข…๋ฅ˜์˜ ์˜๊ตฌ .conf ๋งŒ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค (.example ํŒจํ„ด ๊ธˆ์ง€ โ€” ๋‹จ์ผ ํ™•์žฅ์ž ์ •์ฑ…):

ํŒŒ์ผ ์—ญํ•  ๋™์ž‘
default.conf catch-all ๋‹จ๋… ์ฑ…์ž„ listen 80 default_server + listen 443 ssl default_server + server_name _ + return 444 / ssl_reject_handshake on. ์•Œ ์ˆ˜ ์—†๋Š” Host/SNI ์ฐจ๋‹จ. default_server ๊ฐ€ ์ •์˜๋˜๋Š” ์œ ์ผํ•œ ์œ„์น˜
<sample>_<stack>_ng_http.conf ์ฆ‰์‹œ ๊ฒ€์ฆ์šฉ ์˜๊ตฌ sample listen 80; (default_server ์—†์Œ) + server_name localhost www.localhost; ๋กœ ํ•œ์ •. nginx ์ปจํ…Œ์ด๋„ˆ ์‹œ์ž‘ ์‹œ ์ž๋™ ๋กœ๋“œ๋˜์–ด Host: localhost ๋กœ HTTP 200 ์‘๋‹ต ๊ฐ€๋Šฅ
Stack sample conf ํŒŒ์ผ
gunicorn django_sample_gunicorn_ng_http.conf
uvicorn django_sample_uvicorn_ng_http.conf
uwsgi django_sample_uwsgi_ng_http.conf
php (7.3/8.4 ๊ณต์šฉ) sample_php_ng_http.conf

์ฆ‰์‹œ ๊ฒ€์ฆ (compose up ์งํ›„):

curl -H "Host: localhost" http://localhost/    # โ†’ 200

๋ณ„๋„ ํ™œ์„ฑํ™” ๋‹จ๊ณ„ (์˜ˆ: cp .example .conf) ๋ถˆํ•„์š”. sample ์˜ listen 80; ์™€ default.conf ์˜ listen 80 default_server; ๊ฐ€ ๋™์ผ ํฌํŠธ๋ฅผ ๊ณต์œ ํ•ด๋„ default_server ํ‚ค์›Œ๋“œ๊ฐ€ default.conf ์—๋งŒ ์žˆ์–ด ์ถฉ๋Œ ์—†์Œ.

๋„๋ฉ”์ธ๋ณ„ ์šด์˜ conf ์ถ”๊ฐ€: nginx_http_conf.sh -w <webroot> -d <domain> -p 80 -a <appname> -s <serviceport> -n <name> ๋กœ conf.d/<name>_<stack>_ng_http.conf ๋ฅผ ์ƒ์„ฑ โ€” sample ๊ณผ ๋‹ค๋ฅธ server_name ์„ ๊ฐ€์ง€๋ฏ€๋กœ ๊ณต์กดํ•ฉ๋‹ˆ๋‹ค. ์šด์˜ ์‹œ sample ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ํ•ด๋‹น ํŒŒ์ผ์„ conf.d/ ๋ฐ–์œผ๋กœ ์˜ฎ๊ธฐ๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜์„ธ์š” (์˜๊ตฌ .conf ์ด๋ฏ€๋กœ git ์ถ”์ ).

How to develop based on working server

  • User can access using defined folders in docker-compose.yml

    Example -> nginx container has volumes like below that
    
    /www
    /script/
    /etc/nginx/conf.d/
    /etc/nginx/nginx.conf
    /etc/nginx/uwsgi_params
    /ssl/
    /log
    
    • If user run containers at same server, can update code and move files directly from local server folder to container folder.
  • If user use firewall, have to add required port number (refer each docker-compose.yml files)

    Example
    
    ufw allow 80/tcp
    ufw allow 3306/tcp
    

Setting up HTTPS on a web server

  • This step requires running http nginx server

    1. Run nginx_http_conf.sh located in config/web-server/nginx/. Create a conf file for each domain under config/web-server//conf.d/. Generated filenames always end with "_http" (e.g. _gunicorn_ng_http.conf).

    2. Please edit compose/web-service//docker-compose directly and run it according to the service you want to use.

    3. This will run the default nginx using http.

    4. The "docker exec -it bash" command allows users to access docker internals.

    5. The script/letsencrypt.sh shell script file is linked per volume. This allows users to access script files directly from the nginx container.

    6. Run script/letsencrypt.sh and enter information such as web root, domain, and email. This script automatically creates an SSL key for your volume if it does not exist.

    7. If you entered all keys correctly, use the exit command to exit the container.

    8. Now we need to create a conf file for https and delete the existing file.

    9. Run nginx_https_conf.sh located in config/web-server/nginx/. Create a conf file for each domain under config/web-server//conf.d/.

    10. Users must remove the http conf file from config/web-server//conf.d/.

    11. Run the โ€œdocker-compose restartโ€ command in the compose folder. You can also use the โ€œdocker-compose stopโ€ and โ€œdocker-compose startโ€ commands in the compose folder. Do not use the "docker-compose down" command. Related configuration files may be deleted.

    12. Certbot ๊ฐฑ์‹  cron ์€ ์ปจํ…Œ์ด๋„ˆ ์•ˆ์— ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค (docker/<stack>/entrypoint-with-cron.sh ๊ฐ€ ๋ถ€ํŒ… ์‹œ ๋“ฑ๋ก). ํ˜ธ์ŠคํŠธ์—์„œ ๋ณ„๋„ crontab ์„ค์ • / ์™ธ๋ถ€ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์€ ๋ถˆํ•„์š” ํ•ฉ๋‹ˆ๋‹ค. ๊ฐฑ์‹  ์‹œ --deploy-hook "nginx -t && nginx -s reload" ๋กœ ์ธ์ฆ์„œ๊ฐ€ ๋ฐ”๋€ ๊ฒฝ์šฐ์—๋งŒ nginx ๊ฐ€ graceful reload ํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ cron ์ง„์‹ค ์†Œ์Šค๋Š” ยง3 "๊ฐฑ์‹  cron ์˜ ์ง„์‹ค ์†Œ์Šค" ์ฐธ์กฐ.

    13. ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ cron ๋“ฑ๋ก ํ™•์ธ: docker compose exec webserver crontab -l ๋˜๋Š” docker compose exec gunicorn-app crontab -l (๊ฐ stack ์˜ entrypoint ๊ฐ€ ๋“ฑ๋ก).

์šด์˜์ž ๊ฐ€์ด๋“œ (Operator's Manual)

๋ณธ ์„น์…˜์€ ์ธํ”„๋ผ/์šด์˜์ž ๊ด€์ ์—์„œ ๋ณธ ํ”„๋กœ์ ํŠธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐฐํฌยท์šด์˜ํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ์ „ ์ง€์‹, ๊ถŒ์žฅ ์„ค์ •, ๊ทธ๋ฆฌ๊ณ  ์ฃผ์˜ํ•ด์•ผ ํ•  ๋™์ž‘์„ ์ •๋ฆฌํ•œ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์ „์— ๋ฐ˜๋“œ์‹œ ์ฝ์–ด์ฃผ์„ธ์š”.

0. ๊ธฐ์ค€ ํ™˜๊ฒฝ ๋ฐ ๋ฐฐํฌ ์ •์ฑ…

ํ•ญ๋ชฉ ๊ฐ’ ๋น„๊ณ 
์„œ๋ฒ„ ์‚ฌ์–‘ 8 core / 8 GB RAM ๋ณธ README ์˜ ๋ชจ๋“  ํŠœ๋‹ ์ˆ˜์น˜๋Š” ์ด ๊ธฐ์ค€์—์„œ ์‚ฐ์ •๋จ. ์‚ฌ์–‘์ด ๋‹ค๋ฅด๋ฉด ์›Œ์ปค/ํ’€ ์ˆ˜์น˜ ์žฌ์‚ฐ์ • ํ•„์š”
๋ฐฐํฌ ๋ฐฉ์‹ ์ˆ˜๋™ stop/start (๋ฌด์ค‘๋‹จ ๋ฏธ๊ณ ๋ ค) docker compose stop โ†’ ์ฝ”๋“œ ๊ฐฑ์‹  โ†’ docker compose start. zero-downtime / blue-green / rolling ๋ฏธ์ง€์›
์ปจํ…Œ์ด๋„ˆ ๋ฒ ์ด์Šค ubuntu:24.04 (LTS), nginx:1.27-bookworm latest ํƒœ๊ทธ ๊ธˆ์ง€ โ€” ์žฌํ˜„ ๊ฐ€๋Šฅ ๋นŒ๋“œ ๋ณด์žฅ
Python 3.14.0 (์†Œ์Šค ์ปดํŒŒ์ผ, multi-stage builder + SHA256 ๊ฒ€์ฆ) ๋นŒ๋“œ ์‹œ๊ฐ„ ๊ธธ์ง€๋งŒ ๋Ÿฐํƒ€์ž„ 5-10% ๋น ๋ฆ„. SHA256 ARG default = 2299dae5...e9f3e9 (ยง0.5.4)
PHP 7.3 (legacy) romeoz/docker-phpfpm:7.3 docker/php-fpm/Dockerfile-7.3. ๋ฉ€ํ‹ฐ๋ฒ„์ „ ๊ฒฝ๋กœ(/etc/php/7.3/fpm/...). ์—…์ŠคํŠธ๋ฆผ ๋น„์œ ์ง€๋ณด์ˆ˜ โ€” ์‹ ๊ทœ ๋ฐฐํฌ๋Š” 8.4 ๊ถŒ์žฅ
PHP 8.4 (current) php:8.4-fpm-bookworm (๊ณต์‹) docker/php-fpm/Dockerfile-8.4. ๋‹จ์ผ ๊ฒฝ๋กœ(/usr/local/etc/php{,-fpm.d}/...). php.ini ๋Š” 8.x ํ˜ธํ™˜์œผ๋กœ ํŒจ์น˜๋จ (config/app-server/php-8.4/php_ini/php.ini)
Redis redis:7.4-alpine + protected-mode yes + requirepass Alpine ๋ฒ ์ด์Šค๋กœ ์ด๋ฏธ์ง€ 100 MB ์ดํ•˜. ์ธ์ฆ ๊ฐ•์ œ (ยง0.5.2)
Flower mher/flower:2.0.1 master ํƒœ๊ทธ๋Š” ์žฌํ˜„์„ฑ ์—†์œผ๋ฏ€๋กœ ๊ธˆ์ง€. FLOWER_BASIC_AUTH ํ•„์ˆ˜
์ด๋ฏธ์ง€ ํƒœ๊ทธ (์ด ํ”„๋กœ์ ํŠธ๊ฐ€ ๋นŒ๋“œ) devspoon-py-app:latest, devspoon-uwsgi-app:latest, devspoon-nginx:latest, devspoon-php-app:{7.3,8.4} compose image: ๋ช…์‹œ๋กœ ์Šคํƒ / ์„œ๋น„์Šค ๊ฐ„ ์žฌ์‚ฌ์šฉ (ยง0.5.4)

์™œ "์ˆ˜๋™ stop/start" ๊ฐ€ ๊ธฐ๋ณธ ์ •์ฑ…์ธ๊ฐ€?

  • ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๋‹จ์ผ ์„œ๋ฒ„(8c/8g) ์šด์šฉ์„ 1์ฐจ ํƒ€๊นƒ์œผ๋กœ ํ•˜๋ฉฐ, ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ/์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ(K8s)๊ฐ€ ์—†๋Š” ํ™˜๊ฒฝ์—์„œ ๊ฐ€์žฅ ๋‹จ์ˆœยท์•ˆ์ „ํ•œ ๋ฐฐํฌ ๋ชจ๋ธ.
  • ๋ฌด์ค‘๋‹จ(graceful HUP reload) ์„ ํฌ๊ธฐํ•œ ๋Œ€์‹  ๋ฉ”๋ชจ๋ฆฌ ์ ˆ๊ฐ ์„ ์šฐ์„ :
    • gunicorn preload_app=True (Copy-on-Write๋กœ ์›Œ์ปค ๋ฉ”๋ชจ๋ฆฌ 20-40% ๊ฐ์†Œ)
    • uwsgi lazy-apps=false (๋งˆ์Šคํ„ฐ์—์„œ 1ํšŒ ๋กœ๋“œ ํ›„ fork)
  • ๋ฌด์ค‘๋‹จ์ด ํ•„์š”ํ•ด์ง€๋Š” ์‹œ์ ์€ ๋ณ„๋„ PR/๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅ.

0.5. 2026-05 ๋ณด์•ˆ/๊ตฌ์กฐ ํ•˜๋“œ๋‹ (Recent Hardening Sweep)

๋ณธ ์ ˆ์€ ๊ฐ€์žฅ ์ตœ๊ทผ์— ์ผ๊ด„ ์ ์šฉ๋œ ์ •์ฑ… ๋ณ€๊ฒฝ์„ ๋ชจ์•„ ๋‘”๋‹ค โ€” ์ด์ „ README ์˜ ๊ธฐ๋ณธ๊ฐ’๊ณผ ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด ์žˆ์œผ๋‹ˆ ์šด์˜์ž๋Š” ๋ฐ˜๋“œ์‹œ ๋ณธ ์ ˆ์„ ํ™•์ธ ํ›„ ยง1 ์ดํ•˜ ์šด์˜ ๊ฐ€์ด๋“œ๋ฅผ ์ฝ์„ ๊ฒƒ.

0.5.1 ์ž๊ฒฉ์ฆ๋ช… ์™ธ๋ถ€ํ™” โ€” .env ์™€ .env-example

  • 6๊ฐœ ์Šคํƒ(daphne / gunicorn / uvicorn / uwsgi / php-7.3 / php-8.4) ๊ฐ๊ฐ์˜ compose/web-service/<stack>/.env ๋Š” git ์ถ”์  ๋Œ€์ƒ์—์„œ ์ œ์™ธ. ๋™์ผ ํด๋”์˜ .env-example ๋งŒ ์ถ”์ ๋˜๋ฉฐ, ์‹ ๊ทœ ํ™˜๊ฒฝ์€ cp .env-example .env ํ›„ ์ž๊ฒฉ์ฆ๋ช…์„ ์ฑ„์›Œ ์‹œ์ž‘.
  • .gitignore ์˜ ํŒจํ„ด: **/.env (ignore) + !**/.env-example (์˜ˆ์™ธ ์ถ”์ ). ๊ธฐ์กด์— ์ถ”์ ๋˜๋˜ 5๊ฐœ .env ๋Š” git rm --cached ๋กœ untrack ๋จ (์ž‘์—…ํŠธ๋ฆฌ ๋ณด์กด).
  • compose ์˜ ${VAR:?error} ๊ฒ€์ฆ: ์ž๊ฒฉ์ฆ๋ช… ํ‚ค(REDIS_PASSWORD / FLOWER_ID / FLOWER_PWD) ๊ฐ€ ๋ฏธ์„ค์ •/๋นˆ๋ฌธ์ž์—ด์ด๋ฉด docker compose up ๋‹จ๊ณ„์—์„œ ์ฆ‰์‹œ fail-fast โ†’ "๋น„๋ฐ€๋ฒˆํ˜ธ ๋นˆ๊ฐ’ ๊ธฐ๋™" ์‚ฌ๊ณ  ์ฐจ๋‹จ.
  • ๋กœ๊ทธ ์˜ต์…˜ ํ‚ค(LOG_DRIVER / LOG_OPT_MAXF / LOG_OPT_MAXS) ๋Š” ${VAR:-default} ๋กœ fallback ์ฒ˜๋ฆฌ๋˜์–ด .env ๋ˆ„๋ฝ์—๋„ ๋ฌด์˜ํ–ฅ.

0.5.2 Redis ์ธ์ฆ ๊ฐ•ํ™” (protected-mode yes + requirepass)

  • 6๊ฐœ redis.conf ๋ชจ๋‘ protected-mode no โ†’ yes. ๋™์ผ docker network ๋‚ด๋ถ€์—์„œ๋„ ์ธ์ฆ ์—†์ด๋Š” ์–ด๋–ค ํ‚ค์—๋„ ์ ‘๊ทผ ๋ถˆ๊ฐ€.
  • requirepass ๊ฐ’์€ redis.conf ๊ฐ€ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ณด๊ฐ„์„ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ compose ์˜ command: redis-server ... --requirepass ${REDIS_PASSWORD} ๋กœ ์™ธ๋ถ€ ์ฃผ์ž….
  • redis ์ปจํ…Œ์ด๋„ˆ์— healthcheck ์ถ”๊ฐ€:
    healthcheck:
      test: ["CMD-SHELL", "redis-cli --no-auth-warning -a \"$$REDIS_PASSWORD\" ping | grep -q PONG"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 10s
  • ์•ฑ / celery / celery-beat / flower ์˜ depends_on: redis ๊ฐ€ condition: service_healthy ๋กœ ๊ฐ•ํ™” โ€” redis ๊ฐ€ ์ธ์ฆ ์‘๋‹ต์„ ์ค„ ๋•Œ๊นŒ์ง€ ์˜์กด ์„œ๋น„์Šค ๊ธฐ๋™์„ ๋ณด๋ฅ˜ํ•ด race condition ์ฐจ๋‹จ.

0.5.3 CELERY_BROKER_URL ์˜ SSOT ํ™” (drift ์ฐจ๋‹จ)

  • ๊ณผ๊ฑฐ .env ์— CELERY_BROKER_URL=redis://:PASS@redis:6379/3 ํ˜•ํƒœ๋กœ ๋ณ„๋„ ๋ณด๊ด€ โ†’ REDIS_PASSWORD ์™€ ๋‘ ๊ฐ’์„ ๋™๊ธฐํ™”ํ•ด์•ผ ํ•˜๋Š” drift ์œ„ํ—˜ ์กด์žฌ.
  • ์ด๋ฒˆ ๋ณ€๊ฒฝ์œผ๋กœ .env ์˜ CELERY_BROKER_URL ํ‚ค ์ œ๊ฑฐ. compose ์˜ celery / celery-beat / flower environment: ์—์„œ redis://:${REDIS_PASSWORD:?...}@redis:6379/3 ๋กœ ์ง์ ‘ ํ•ฉ์„ฑ โ†’ REDIS_PASSWORD ํ•œ ๊ฐ’๋งŒ ๊ฐฑ์‹ ํ•˜๋ฉด 4๊ฐœ ์„œ๋น„์Šค๊ฐ€ ๋™์‹œ์— ๋™๊ธฐํ™”.
  • ์ถ”๊ฐ€ ํšจ๊ณผ: ๊ณผ๊ฑฐ์—๋Š” celery / celery-beat ์ปจํ…Œ์ด๋„ˆ์— CELERY_BROKER_URL ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์ฃผ์ž…๋˜์ง€ ์•Š์•„ Django settings ๊ฐ€ os.environ['CELERY_BROKER_URL'] ์„ ์ฝ์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’ amqp://localhost ๋กœ ๋–จ์–ด์ง€๋Š” ์ž ์žฌ ๋ฒ„๊ทธ ์กด์žฌ โ†’ ๋ณธ ๋ณ€๊ฒฝ์œผ๋กœ ํ•ด์†Œ.

0.5.4 Docker image ๋นŒ๋“œ ์ •์ฑ… ๋ณ€๊ฒฝ

  • docker/gunicorn/Dockerfile ์™€ docker/uwsgi/Dockerfile ์„ multi-stage ๋กœ ์žฌ๊ตฌ์„ฑ:
    • builder stage: ubuntu:24.04 + build-essential + *-dev libs + Python 3.14 ์†Œ์Šค ์ปดํŒŒ์ผ + pip ์‚ฌ์ „ ์„ค์น˜ (native build ํ•„์š”ํ•œ mysqlclient/psycopg2 ๋“ฑ ํฌํ•จ).
    • runtime stage: ubuntu:24.04 + ๋Ÿฐํƒ€์ž„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋งŒ + percona-xtrabackup-84 + pgbackrest + cron + logrotate. build-essential / pkg-config ์ œ๊ฑฐ๋˜์–ด ์ด๋ฏธ์ง€ ์•ฝ 500MB ์ ˆ๊ฐ.
  • Python tarball SHA256 ๊ฒ€์ฆ ์ถ”๊ฐ€:
    • ARG PYTHON_SHA256=2299dae542d395ce3883aca00d3c910307cd68e0b2f7336098c8e7b7eee9f3e9 (Python 3.14.0 ๊ณต์‹, 2026-05-18 ํ™•์ธ).
    • ๋นŒ๋“œ ๋‹จ๊ณ„์—์„œ sha256sum -c ๊ฐ€ ๋น„0 ์ข…๋ฃŒํ•˜๋ฉด RUN ์ค‘๋‹จ โ†’ ๊ณต๊ธ‰๋ง ๋ณ€์กฐ ๊ฐ์ง€.
    • ๋ฒ„์ „ bump ์‹œ python.org Files ํ‘œ ๋˜๋Š” .sigstore bundle ๊ฒ€์ฆ ํ›„ ARG ๊ฐ’ ๊ฐฑ์‹ .
  • compose ์˜ image: ๋ช…์‹œ ํƒœ๊ทธ๋กœ stack ๊ฐ„ / ์„œ๋น„์Šค ๊ฐ„ ์žฌ์‚ฌ์šฉ:
    • devspoon-py-app:latest โ€” gunicorn / daphne / uvicorn 3๊ฐœ ์Šคํƒ + ๊ฐ ์Šคํƒ ๋‚ด celery / celery-beat ๊ฐ€ ๊ณต์œ .
    • devspoon-uwsgi-app:latest โ€” uwsgi ์Šคํƒ ์ „์šฉ.
    • devspoon-nginx:latest, devspoon-php-app:7.3 / devspoon-php-app:8.4.
    • ํšจ๊ณผ: docker compose build ๊ฐ€ ๋™์ผ ์ปจํ…์ŠคํŠธ๋ฅผ 3-4ํšŒ ์žฌ๋นŒ๋“œํ•˜๋˜ ๋™์ž‘์ด 1ํšŒ๋กœ ์ถ•์†Œ.

0.5.5 uwsgi.ini ๊ถŒํ•œ ๊ฐ•ํ™”

  • username = root ์ œ๊ฑฐ โ†’ uid = www-data / gid = www-data ํ™œ์„ฑ. master ๊ฐ€ root ๊ฐ€ ์•„๋‹Œ www-data ๋กœ ๋–จ์–ด์ง€๋ฉฐ PHP-FPM ํŒจํ„ด๊ณผ ๋Œ€์นญ (least-privilege).
  • chmod-socket = 666 ์ฃผ์„ํ™” โ€” TCP 8000 ์‚ฌ์šฉ ์‹œ ๋ฌด์˜๋ฏธ. ํ–ฅํ›„ unix socket ์ „ํ™˜ ์‹œ 0660 ์‚ฌ์šฉ ์•ˆ๋‚ด๊ฐ€ ์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ๋กœ ์ถ”๊ฐ€๋จ.
  • sample_uwsgi.ini ์˜ py-autoreload = 1 โ†’ 0 โ€” cookiecut ์‹œ ์šด์˜ ๋ถ€์ ํ•ฉ ๊ธฐ๋ณธ๊ฐ’์ด ๋‹ค์‹œ ๋„์ง€์ง€ ์•Š๊ฒŒ ํ†ต์ผ.

0.5.6 ulimits โ€” ๋ณ€์ˆ˜ ์ •์˜ ํ›„ ๋ช…์‹œ์  ๋น„ํ™œ์„ฑ

  • .env ์— ULIMIT_NOFILE_SOFT=65535 / ULIMIT_NOFILE_HARD=65535 ๋ณ€์ˆ˜ ์ •์˜.
  • 6๊ฐœ compose ์˜ webserver ์„œ๋น„์Šค์— ulimits ๋ธ”๋ก์€ ์ฃผ์„ ์ฒ˜๋ฆฌ ์ƒํƒœ๋กœ ํฌํ•จ โ€” ํ˜ธ์ŠคํŠธ docker daemon ์˜ ๊ธฐ๋ณธ LimitNOFILE (ํ†ต์ƒ 1048576) ์ด 65535 ๋ฅผ ์ถฉ๋ถ„ํžˆ ์ˆ˜์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ. ํ˜ธ์ŠคํŠธ OS ์ฐจ์ด๋กœ nofile ํ•œ๋„๊ฐ€ 65535 ๋ฏธ๋งŒ์œผ๋กœ ์ž˜๋ฆฌ๋Š” ํ™˜๊ฒฝ(RHEL / podman / ์ผ๋ถ€ K8s ๋…ธ๋“œ)์—์„œ๋งŒ ์ฃผ์„ ํ•ด์ œํ•˜์—ฌ ๋ช…์‹œ ํ™œ์„ฑํ™”.

0.5.7 redis-stats ์ œ๊ฑฐ

  • insready/redis-stat:latest (2017๋…„ ์ดํ›„ ๋ฏธ๊ด€๋ฆฌ, ๋ณด์•ˆ ํŒจ์น˜ ์—†์Œ) ๊ฐ€ ๋ชจ๋“  6๊ฐœ compose ์—์„œ ์‚ญ์ œ๋จ. ํ˜ธ์ŠคํŠธ 63790/tcp ๋…ธ์ถœ๋„ ํ•จ๊ป˜ ์ œ๊ฑฐ.
  • ๋Œ€์ฒด๊ฐ€ ํ•„์š”ํ•˜๋ฉด RedisInsight ๋˜๋Š” oliver006/redis_exporter (Prometheus์šฉ) ๋„์ž… ๊ถŒ์žฅ.

0.5.8 PHP-FPM Dockerfile tzdata ํŒจํ„ด ํ†ต์ผ

  • Dockerfile-7.3 / Dockerfile-8.4 ์—์„œ dpkg-reconfigure tzdata ์ œ๊ฑฐ. gunicorn / uwsgi / nginx Dockerfile ๊ณผ ๋™์ผํ•˜๊ฒŒ ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ํŒจํ„ด์œผ๋กœ ํ†ต์ผ.
  • Dockerfile-7.3 ์˜ ์ค‘๋ณต install layer 6๊ฐœ๋ฅผ ๋‹จ์ผ RUN ์œผ๋กœ ์••์ถ•.

0.5.9 aisum-infrakit ์™€์˜ ์ •ํ•ฉํ™” ๋™๊ธฐํ™” (2026-06)

aisum-infrakit (๋ณธ ํ”„๋กœ์ ํŠธ์˜ ์‚ฌ๋‚ด ํŒŒ์ƒ๋ณธ) ์˜ ์šด์˜ ๊ฒ€์ฆ ์‚ฐ์ถœ๋ฌผ์„ ์—ญ๋จธ์ง€ํ•˜์—ฌ ๋‹ค์Œ ํ•ญ๋ชฉ์ด ์ผ๊ด„ ์ •ํ•ฉํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค:

ํ•ญ๋ชฉ ๋ณ€๊ฒฝ ์˜๋„
ํด๋”๋ช… compose/web_service/ โ†’ compose/web-service/ dash naming aisum-infrakit ์™€ ๋™์ผ โ€” ์™ธ๋ถ€ ๋ฌธ์„œ/์Šคํฌ๋ฆฝํŠธ ํ˜ธํ™˜
ํŒŒ์ผ๋ช… .env.example โ†’ .env-example (6๊ฐœ ์Šคํƒ) dash naming aisum-infrakit ์™€ ๋™์ผ
config/web-server/nginx/uvicorn/ ์‹ ์„ค ๋””๋ ‰ํ† ๋ฆฌ ์ถ”๊ฐ€ ๊ธฐ์กด์—” nginx_uvicorn ์ปดํฌ์ฆˆ ์Šคํƒ๋งŒ ์žˆ๊ณ  nginx conf ๊ฐ€ ๋ˆ„๋ฝ โ€” uvicorn ๋„๋ฉ”์ธ conf ๋ฅผ ์ƒ์„ฑํ•  ๋ฐฉ๋ฒ•์ด ์—†์—ˆ๋‹ค
config/app-server/uvicorn/gunicorn_uvicorn.conf.py ์‹ ์„ค gunicorn+UvicornWorker ์„ค์ • ASGI ์œ„์—์„œ gunicorn ์œผ๋กœ ์›Œ์ปค๋ฅผ ๋„์šฐ๋Š” ํŒจํ„ด ์ง€์›
dhparam ๋ฐฑ์—…/๋ณต์› ๋ฉ”์ปค๋‹ˆ์ฆ˜ ssl/certs:/etc/ssl/certs ์•ˆํ‹ฐํŒจํ„ด ์ œ๊ฑฐ, ssl/dhparam:/etc/nginx/dhparam-backup ์œผ๋กœ ๊ต์ฒด + nginx Dockerfile ์— ๋นŒ๋“œ ์‹œ ๊ตฝ๊ธฐ + entrypoint hook (ยง3 dhparam ์˜์†ํ™” ์ ˆ) certbot ์‹œ์Šคํ…œ CA ๊ฐ€๋ฆผ ์ œ๊ฑฐ, docker compose down/up ํ›„์—๋„ ๋™์ผ ํ‚ค ์œ ์ง€
script/test_run/ ์‹ ์„ค (21 ๊ฐœ ์Šคํฌ๋ฆฝํŠธ) aisum-infrakit ์˜ ํšŒ๊ท€ ๊ฒ€์ฆ ์ž์‚ฐ ๋„์ž… โ€” s0_prereq, s1b_exit_check, s2_build, s3_stack_smoke, s5_https, s6_regression, ssl_diag, verify_block ๋“ฑ + audit ๋ผ์šด๋“œ์—์„œ ์ถ”๊ฐ€๋œ verify_dhparam_lifecycle / verify_dhparam_host_wins / verify_conf_generators / verify_compose_yml / verify_nginx_standalone ๊ฒ€์ฆ ์ž๋™ํ™” (ยง10.3 ์ฐธ์กฐ). ๊ธฐ์กด script/test/preflight.sh / verify-ngxblocker.sh ์™€ ๊ณต์กด
script/logrotate/* ์— su root root ์ถ”๊ฐ€ logrotate ์˜ "potentially insecure mode" ๊ฑฐ๋ถ€ ํšŒํ”ผ (WSL /mnt/c 0777 mount ํ™˜๊ฒฝ) bind mount ํ™˜๊ฒฝ์—์„œ ๋กœํ…Œ์ด์…˜ ์‹คํŒจ ํ•ด์†Œ
docker/gunicorn/Dockerfile / docker/uwsgi/Dockerfile ์˜์กด์„ฑ ๋ณด๊ฐ• builder: libbz2-dev liblzma-dev ์ถ”๊ฐ€ / runtime: libbz2-1.0 liblzma5 libnsl2 libuuid1 ์ถ”๊ฐ€ (uwsgi ๋Š” libpcre3* libxml2* ์ถ”๊ฐ€) Python stdlib (bz2 / lzma) ์™€ uwsgi ๋ผ์šฐํŒ…/ํ”Œ๋Ÿฌ๊ทธ์ธ dlopen ์‹คํŒจ ๋ฐฉ์ง€
entrypoint-with-cron.sh 3์ข… sanitize ๋กœ์ง ์ถ”๊ฐ€ bind-mount ๋œ logrotate dropin ์„ /run/logrotate.d/ ๋กœ mode 0644 ์‚ฌ๋ณธํ™” WSL 0777 mount ์—์„œ logrotate "potentially dangerous mode" ๊ฑฐ๋ถ€ ํšŒํ”ผ
www/fastapi_sample/ / www/flask_sample/ / www/certbot/ ์‹ ์„ค aisum-infrakit ์˜ ์ƒ˜ํ”Œ ์•ฑ ๋„์ž… uvicorn (FastAPI) / ์ผ๋ฐ˜ WSGI (Flask) ์Šคํƒ์˜ ๋™์ž‘ ํ™•์ธ์šฉ. www/certbot/ ์€ ACME webroot ํ‘œ์ค€ ์œ„์น˜
script/test_run/* ์˜ ํ•˜๋“œ์ฝ”๋”ฉ ROOT ๊ฒฝ๋กœ /mnt/c/.../aisum-infrakit โ†’ /mnt/c/.../devspoon-web ๋กœ ์ผ๊ด„ ์น˜ํ™˜๋จ ๋™์ผ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ devspoon-web ์—์„œ ์ฆ‰์‹œ ๋™์ž‘

๋ณด์กด (๋ฎ์–ด์“ฐ์ง€ ์•Š์Œ):

  • nginx_daphne ์Šคํƒ (devspoon ๊ณ ์œ ), script/logrotate/daphne/*, docs/operations-guide/nginx-hardening/*
  • docker/php-fpm/Dockerfile-7.3 / Dockerfile-8.4 ์˜ PHP ๋ฒ„์ „ ๋ถ„๋ฆฌ (aisum ์€ ๋‹จ์ผ PHP 7.2 ๋งŒ)
  • docker/{gunicorn,uwsgi,php-fpm}/entrypoint-with-cron.sh ๊ตฌ์กฐ (aisum ์€ Dockerfile ์ธ๋ผ์ธ ํŒจํ„ด)
  • redis.conf ์˜ protected-mode yes (aisum ์€ no. devspoon ์ •์ฑ… ์šฐ์›”)
  • CELERY_BROKER_URL ์˜ compose ํ•ฉ์„ฑ(SSOT) (aisum ์€ .env ์— ๋ณ„๋„ ๋ณด๊ด€)
  • script/letsencrypt.sh (devspoon ๋ฒ„์ „์ด ๋” ์ง„ํ™”)
  • nginx_php-7.3/8.4 ์˜ 2๊ฐœ ๋ณ‘ํ–‰ ์Šคํƒ + config/app-server/php-7.3 / php-8.4 ๋ถ„๋ฆฌ

1. App-server ์›Œ์ปค/ํ’€ ์‚ฐ์ • ๊ทผ๊ฑฐ

8 core / 8 GB ๊ธฐ์ค€ ๋ฉ”๋ชจ๋ฆฌ ๊ฐ€์šฉ๋Ÿ‰ ๊ณ„์‚ฐ: 8 GB - (OS + nginx + redis + ํ—ค๋“œ๋ฃธ โ‰ˆ 2 GB) = ์•ฝ 6 GB.

์„œ๋น„์Šค ํ•ต์‹ฌ ์ˆ˜์น˜ ์‚ฐ์ • ๊ทผ๊ฑฐ
gunicorn (Django sync) workers=9, threads=4 (cores + 1) ๋ณด์ˆ˜์น˜. ์›Œ์ปค๋‹น ~250 MB ร— 9 โ‰ˆ 2.25 GB. threads=4 ๋กœ DB I/O ๋Œ€๊ธฐ ํก์ˆ˜ โ†’ ๋™์‹œ ์Šฌ๋กฏ 36
uvicorn (FastAPI ASGI) workers=8 (UvicornWorker) ๋น„๋™๊ธฐ ์›Œ์ปค๋Š” ๋‹จ์ผ ์ด๋ฒคํŠธ ๋ฃจํ”„๋กœ ๋‹ค์ˆ˜ ๋™์‹œ ์ฒ˜๋ฆฌ. 1 worker / core. ์›Œ์ปค๋‹น ~600 MB ร— 8 โ‰ˆ 4.8 GB
uwsgi (Django) processes=8, threads=4 sync ์›Œ์ปค. harakiri=60, reload-on-rss=800MB (๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์ž๋™ ๋ณต๊ตฌ)
daphne (ASGI WS) ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค daphne ๋Š” ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์Šค ๋ฏธ์ง€์›. ๋™์‹œ websocket ์ˆ˜์ฒœ์€ ๋‹จ์ผ ์ธ์Šคํ„ด์Šค๋กœ ๊ฐ€๋Šฅ. ๋” ํ•„์š” ์‹œ nginx upstream + ๋‹ค์ค‘ ์ปจํ…Œ์ด๋„ˆ
celery worker --concurrency=8 --max-tasks-per-child=2000 prefork ํ’€, ์ฝ”์–ด ๋งค์นญ. ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์–ด ์œ„ํ•ด 2000 ํƒœ์Šคํฌ๋งˆ๋‹ค ์›Œ์ปค ์žฌ๊ธฐ๋™
php-fpm (7.3/8.4 ๊ณต์šฉ) pm.max_children=40, start=8, min_spare=8, max_spare=24 ์›Œ์ปค๋‹น ~80 MB ร— 40 โ‰ˆ 3.2 GB. request_terminate_timeout=60s ๋กœ hang ๋ฐฉ์ง€. ๋‘ ๋ฒ„์ „ ๋ชจ๋‘ ๋™์ผ pool ์ •์ฑ…์„ ์ ์šฉ โ€” ๊ฐ ๋ฒ„์ „ ํด๋”(config/app-server/php-7.3 / php-8.4)์˜ pool.d/sample_php.conf ๋‚ด์šฉ์€ ๋™์ผํ•˜๋ฉฐ, ์ฐจ์ด๋Š” php.ini ์˜ 8.x ํ˜ธํ™˜ ํŒจ์น˜๋ฟ

์ฃผ์˜ โ€” preload_app=True ์˜ ๋ถ€์ˆ˜ ํšจ๊ณผ:

  • DB ์ปค๋„ฅ์…˜์„ ๋ชจ๋“ˆ import ์‹œ์ ์— ์—ด๋ฉด fork ํ›„ ์›Œ์ปค๋“ค์ด ๋™์ผ ์†Œ์ผ“์„ ๊ณต์œ  โ†’ ์ถฉ๋Œ ๊ฐ€๋Šฅ.
  • ํ•ด๊ฒฐ: DB ์ปค๋„ฅ์…˜์€ ์ฒซ ์š”์ฒญ ์‹œ lazy ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, post_fork ํ›…์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ์žฌ์ƒ์„ฑ.
  • Django ORM ์€ ์ž๋™ ์ฒ˜๋ฆฌ๋˜์ง€๋งŒ, SQLAlchemy + raw psycopg2 ๋“ฑ์€ ์ง์ ‘ ์ฑ™๊ธธ ๊ฒƒ.

2. ๋กœ๊ทธ ๊ตฌ์กฐ (์‹๋ณ„ ๊ฐ€๋Šฅํ•œ ์ผ์›ํ™” ๊ตฌ์กฐ)

๋ชจ๋“  ๋กœ๊ทธ๋Š” ํ˜ธ์ŠคํŠธ ์ธก ./log/ ํ•˜๋‚˜๋กœ ์ผ์›ํ™” ๋˜๋ฉฐ, ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ๋Š” /log/ ๋กœ ๋งˆ์šดํŠธ๋ฉ๋‹ˆ๋‹ค.

log/
โ”œโ”€โ”€ nginx/              # nginx access/error + certbot ๊ฐฑ์‹  ๋กœ๊ทธ
โ”œโ”€โ”€ gunicorn/           # gunicorn_access.log, gunicorn_error.log
โ”‚   โ”œโ”€โ”€ celery/         # worker-*.log
โ”‚   โ””โ”€โ”€ celerybeat/     # celerybeat.log
โ”œโ”€โ”€ uvicorn/            # uvicorn_access.log, uvicorn_error.log
โ”‚   โ”œโ”€โ”€ celery/
โ”‚   โ””โ”€โ”€ celerybeat/
โ”œโ”€โ”€ uwsgi/              # <project>-uwsgi.log, daemonize, _access.log
โ”‚   โ”œโ”€โ”€ celery/
โ”‚   โ””โ”€โ”€ celerybeat/
โ”œโ”€โ”€ daphne/             # stdout.log, error.log, access.log
โ”‚   โ”œโ”€โ”€ celery/
โ”‚   โ””โ”€โ”€ celerybeat/
โ”œโ”€โ”€ php-fpm/            # access.log, www-error.log, slow.log
โ””โ”€โ”€ supervisor/         # (์˜ˆ์•ฝ ์Šฌ๋กฏ)
  • ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ฃฝ์–ด๋„ ๋กœ๊ทธ๋Š” ๋ณด์กด๋จ (ํ˜ธ์ŠคํŠธ ๋ณผ๋ฅจ ๋งˆ์šดํŠธ).
  • ๋ชจ๋“  ํด๋”๋Š” .gitkeep ์œผ๋กœ ์ถ”์  (์ง์ ‘ ๋˜๋Š” ์„œ๋ธŒํด๋” ํ†ตํ•ด).
  • ์‹ ๊ทœ ์„œ๋น„์Šค ์ถ”๊ฐ€ ์‹œ log/<service>/ ํด๋”์™€ .gitkeep ์„ ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€.

Logrotate

  • ํ˜ธ์ŠคํŠธ script/logrotate/<service>/<service> โ†’ ์ปจํ…Œ์ด๋„ˆ /etc/logrotate.d/<service> ๋กœ ๋งˆ์šดํŠธ.
  • ์ •์ฑ…: copytruncate ๋ฐฉ์‹ โ€” ์„œ๋น„์Šค ์žฌ์‹œ์ž‘ ์—†์ด ๋กœํ…Œ์ด์…˜ ๊ฐ€๋Šฅ (๋กœํ…Œ์ด์…˜ ์ˆœ๊ฐ„ ๊ทน์†Œ๋Ÿ‰ ๋กœ๊ทธ ๋ˆ„๋ฝ ๊ฐ€๋Šฅ์„ฑ์€ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„).
  • ๋ณด๊ด€ ์ฃผ๊ธฐ: ๊ธฐ๋ณธ 30์ผ (php-fpm access๋Š” 7์ผ, slow log๋Š” 90์ผ โ€” ์ง„๋‹จ ์šฐ์„ ).
  • ์ ์šฉ ํ™•์ธ:
    docker exec -it <container> logrotate -d /etc/logrotate.d/<service>

3. SSL / HTTPS / Certbot ์ž๋™ ๊ฐฑ์‹ 

๊ฐฑ์‹  cron ์˜ ์ง„์‹ค ์†Œ์Šค (single source of truth)

  • docker/nginx/Dockerfile ํ•œ ๊ณณ๋งŒ ์ด ์ง„์‹ค ์†Œ์Šค์ž…๋‹ˆ๋‹ค.
  • script/letsencrypt.sh ๋Š” ์ดˆ๊ธฐ ๋ฐœ๊ธ‰ ์ „์šฉ โ€” cron ๋“ฑ๋ก ๋ผ์ธ ์—†์Œ (์˜๋„์  ์ œ๊ฑฐ).
  • ๋“ฑ๋ก๋œ cron:
    0 5 * * 1 certbot renew --quiet --deploy-hook "nginx -t && nginx -s reload" >> /log/nginx/crontab_YYYYMMDD.log 2>&1

์ปจํ…Œ์ด๋„ˆ์—์„œ nginx reload ๋Š” nginx -s reload ๋งŒ ์‚ฌ์šฉ

  • service nginx restart / systemctl reload nginx ๋Š” ์‚ฌ์šฉ ๊ธˆ์ง€ โ€” PID 1 = nginx ์ธ ์ปจํ…Œ์ด๋„ˆ์—์„œ๋Š” ๋™์ž‘ ์•ˆ ํ•จ ๋˜๋Š” ์ „์ฒด ์ปจํ…Œ์ด๋„ˆ ์ข…๋ฃŒ๋ฅผ ์œ ๋ฐœ.
  • nginx -s reload = master ํ”„๋กœ์„ธ์Šค์— SIGHUP ์ „์†ก โ†’ ์ƒˆ ์›Œ์ปค spawn ํ›„ ๊ตฌ ์›Œ์ปค graceful ์ข…๋ฃŒ.
  • nginx -t && ๋กœ ์„ค์ • ํ…Œ์ŠคํŠธ ํ›„์—๋งŒ reload โ€” ์ž˜๋ชป๋œ conf ๊ฐ€ ์ฆ‰์‹œ prod ๋ฐ˜์˜๋˜๋Š” ์‚ฌ๊ณ  ์ฐจ๋‹จ.

cron ๋™์ž‘ ํ™•์ธ

docker exec -it nginx-<service>-webserver bash -c "crontab -l"
docker exec -it nginx-<service>-webserver bash -c "ps aux | grep cron"
# ๋‹ค์Œ ์‹คํ–‰ ๋กœ๊ทธ ํ™•์ธ
tail -f log/nginx/crontab_*.log

์ฒซ ๋ฐœ๊ธ‰ ์ ˆ์ฐจ (์š”์•ฝ)

  1. HTTP ์ „์šฉ nginx conf ๋กœ ์ปจํ…Œ์ด๋„ˆ ๊ธฐ๋™
  2. docker exec -it <nginx-container> bash
  3. /script/letsencrypt.sh ์‹คํ–‰ (webroot / domain / email ์ž…๋ ฅ)
  4. ์ •์ƒ ๋ฐœ๊ธ‰ ํ›„ exit
  5. HTTPS conf ๋กœ ๊ต์ฒด โ†’ docker compose restart

dhparam ์˜์†ํ™” โ€” ์ด๋ฏธ์ง€ ๊ตฝ๊ธฐ + ํ˜ธ์ŠคํŠธ ๋ฐฑ์—…/๋ณต์› ํ›…

๋ฐฐ๊ฒฝ: dhparam(ssl_dhparam) ์€ ๋น„๋ฐ€์ด ์•„๋‹ˆ๊ณ  ๋„๋ฉ”์ธ ์ข…์†๋„ ์•„๋‹ˆ๋ฏ€๋กœ ์ „์ฒด ์Šคํƒ์—์„œ ๋‹จ์ผ ๊ณต์œ ๋ณธ 1 ๊ฐœ ๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ๊ณผ๊ฑฐ์—๋Š” ํ˜ธ์ŠคํŠธ ssl/certs/ ๋ฅผ /etc/ssl/certs ๋กœ ํ†ต์งธ ๋งˆ์šดํŠธํ–ˆ์ง€๋งŒ, ์ด ๊ฒฝ๋กœ๊ฐ€ ์‹œ์Šคํ…œ CA ๋ฒˆ๋“ค(/etc/ssl/certs/ca-certificates.crt) ์„ ๊ฐ€๋ ค certbot ๋ฐœ๊ธ‰์ด ๊นจ์กŒ์Šต๋‹ˆ๋‹ค. ๋ณธ ๋ฒ„์ „์€ ์ด ์•ˆํ‹ฐํŒจํ„ด์„ ์ œ๊ฑฐํ•˜๊ณ  ์ด๋ฏธ์ง€ ๋นŒ๋“œ ์‹œ์ ์— 1ํšŒ ๊ตฝ๊ธฐ + ํ˜ธ์ŠคํŠธ ๋ฐฑ์—…/๋ณต์› ํ›… ์œผ๋กœ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค.

์œ„์น˜ ์—ญํ•  ๋ˆ„๊ฐ€ ๋งŒ๋“œ๋Š”๊ฐ€
docker/nginx/Dockerfile ์„น์…˜ 8 openssl dhparam -out /etc/nginx/dhparam.pem 2048 โ€” ์ด๋ฏธ์ง€์— dhparam ๊ตฝ๊ธฐ ๋นŒ๋“œ ๋‹จ๊ณ„
docker/nginx/Dockerfile ์„น์…˜ 9 / /docker-entrypoint.d/20-dhparam.sh ํ˜ธ์ŠคํŠธ ๋ฐฑ์—…์ด ์žˆ์œผ๋ฉด ๋ณต์›, ์—†์œผ๋ฉด ๋ฐฑ์—… nginx ๊ธฐ๋™ ์ง์ „ hook (๊ณต์‹ ์ด๋ฏธ์ง€์˜ entrypoint.d)
compose/web-service/<stack>/ssl/dhparam/ (ํ˜ธ์ŠคํŠธ) ๋ณต์› ์†Œ์Šค / ๋ฐฑ์—… ๋Œ€์ƒ. /etc/nginx/dhparam-backup/ ๋กœ ๋งˆ์šดํŠธ ์šด์˜์ž ๋˜๋Š” ์ž๋™ ๋ฐฑ์—… hook
/etc/nginx/dhparam.pem (์ปจํ…Œ์ด๋„ˆ) nginx ๊ฐ€ ์‹ค์ œ ์ฐธ์กฐํ•˜๋Š” ํŒŒ์ผ. sample_nginx_https.conf ์˜ ssl_dhparam ์ง€์‹œ์–ด๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ฒฝ๋กœ hook ์ด ๋ณต์› ๋˜๋Š” ๋นŒ๋“œ๋ณธ ์‚ฌ์šฉ

๋™์ž‘ ์‹œ๋‚˜๋ฆฌ์˜ค:

  1. ์ตœ์ดˆ ๊ธฐ๋™ (ํ˜ธ์ŠคํŠธ ssl/dhparam/ ๋น„์–ด ์žˆ์Œ) โ†’ hook ์ด ์ด๋ฏธ์ง€์˜ dhparam.pem ์„ ํ˜ธ์ŠคํŠธ ๋ฐฑ์—… ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ๋ณต์‚ฌ. ๊ฐ™์€ ํ‚ค๊ฐ€ ํ˜ธ์ŠคํŠธ์— ์˜์†ํ™”๋จ.
  2. docker compose down ํ›„ ์žฌ๊ธฐ๋™ โ†’ ํ˜ธ์ŠคํŠธ ๋ฐฑ์—…๋ณธ์ด ์กด์žฌํ•˜๋ฏ€๋กœ hook ์ด ๊ทธ ๋ฐฑ์—…๋ณธ์„ ์ด๋ฏธ์ง€๋ณธ ์œ„์— ๋ฎ์–ด์“ฐ๊ธฐ ๋ณต์›. nginx ๊ฐ€ ์ฒซ ๊ธฐ๋™ ์‹œ์™€ ๋™์ผํ•œ dhparam ํ‚ค๋ฅผ ์‚ฌ์šฉ.
  3. ์ด๋ฏธ์ง€ ์žฌ๋นŒ๋“œ (์˜ˆ: ๋ฒ ์ด์Šค nginx ๋ฒ„์ „ bump โ†’ ๋นŒ๋“œ ์‹œ dhparam ์ด ์ƒˆ๋กœ ๊ตฝํ˜€์ง) โ†’ hook ์ด ํ˜ธ์ŠคํŠธ ๋ฐฑ์—…๋ณธ์„ ์šฐ์„  ์ ์šฉํ•˜์—ฌ ์šด์˜ ํ‚ค ๋™์งˆ์„ฑ ์œ ์ง€. ์ƒˆ dhparam ์„ ์˜๋„์ ์œผ๋กœ ์ฑ„ํƒํ•˜๋ ค๋ฉด ํ˜ธ์ŠคํŠธ ssl/dhparam/dhparam.pem ์„ ์‚ญ์ œ ํ›„ ์žฌ๊ธฐ๋™.

๊ฒ€์ฆ:

# ๋นŒ๋“œ ์งํ›„ ์ด๋ฏธ์ง€ ์•ˆ์— dhparam ์ด ๊ตฝํ˜”๋Š”์ง€
docker run --rm devspoon-nginx:latest cat /etc/nginx/dhparam.pem | head -1
# โ†’ "-----BEGIN DH PARAMETERS-----"

# ์ปจํ…Œ์ด๋„ˆ ๊ธฐ๋™ ํ›„ ํ˜ธ์ŠคํŠธ ๋ฐฑ์—…๋ณธ ํ™•์ธ
ls -la compose/web-service/nginx_gunicorn/ssl/dhparam/
# โ†’ dhparam.pem ์ด ์ƒ์„ฑ๋˜์–ด ์žˆ์–ด์•ผ ํ•จ

# ๋™์ผ ํ‚ค ์‚ฌ์šฉ ์—ฌ๋ถ€ ํ™•์ธ (๋‹ค์ด์ œ์ŠคํŠธ ๋น„๊ต)
docker exec -it nginx-gunicorn-webserver sha256sum /etc/nginx/dhparam.pem
sha256sum compose/web-service/nginx_gunicorn/ssl/dhparam/dhparam.pem
# โ†’ ๋‘ ๊ฐ’์ด ๊ฐ™์œผ๋ฉด ์ •์ƒ

์ž์„ธํ•œ ๊ฒ€์ฆ ์Šคํฌ๋ฆฝํŠธ๋Š” script/test_run/ssl_diag.sh ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (ยง10.3 ์ฐธ์กฐ).

WSL ํ™˜๊ฒฝ ์ฃผ์˜ : ํ˜ธ์ŠคํŠธ ๋ฐฑ์—… dhparam(ssl/dhparam/dhparam.pem) ์ด ์ปจํ…Œ์ด๋„ˆ root ์†Œ์œ ๋กœ ์ƒ์„ฑ๋˜์–ด ํ˜ธ์ŠคํŠธ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ/์‚ญ์ œ ์‹œ ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ ์ž‘์—…ํ•˜๊ฑฐ๋‚˜ ํ˜ธ์ŠคํŠธ์—์„œ sudo ๋กœ ์ฒ˜๋ฆฌํ•˜์„ธ์š” (์ž์„ธํ•œ ์ ˆ์ฐจ๋Š” ยง11.2 ์ฐธ์กฐ).


3.5. ์•…์„ฑ ๋ด‡ / DDoS ์ฐจ๋‹จ โ€” nginx-ultimate-bad-bot-blocker

๊ธฐ์กด ์ •์  bad_bot.conf (500+ ํŒจํ„ด ์ˆ˜๋™ ๊ด€๋ฆฌ) ๋Š” ํ๊ธฐ๋˜๊ณ , ์—…์ŠคํŠธ๋ฆผ์—์„œ 6์‹œ๊ฐ„ ๋‹จ์œ„๋กœ ๊ฐฑ์‹ ๋˜๋Š” nginx-ultimate-bad-bot-blocker ๊ฐ€ ์ด๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

๋นŒ๋“œ ์‹œ์  ๋™์ž‘ (docker/nginx/Dockerfile)

  1. install-ngxblocker / setup-ngxblocker / update-ngxblocker ๋‹ค์šด๋กœ๋“œ
  2. install-ngxblocker -x ์‹คํ–‰ โ†’ ์ปจํ…Œ์ด๋„ˆ ์•ˆ์— ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ bake:
    • /etc/nginx/conf.d/globalblacklist.conf (๋ชจ๋“  ๋ด‡/์Šค์บ๋„ˆ/์Šคํฌ๋ ˆ์ดํผ map ์ •์˜ โ€” $bad_bot, $bad_referer, $validate_referer ๋“ฑ ๋ณ€์ˆ˜ ์ œ๊ณต)
    • /etc/nginx/bots.d/blockbots.conf (server-level ์ฐจ๋‹จ ๋กœ์ง)
    • /etc/nginx/bots.d/ddos.conf (DDoS ํŒจํ„ด ์ฐจ๋‹จ)
    • /etc/nginx/bots.d/{blacklist,whitelist}-*.conf (์šด์˜์ž ์ปค์Šคํ…€์šฉ ๋นˆ ํŒŒ์ผ)

๋Ÿฐํƒ€์ž„ ๋™์ž‘

  • ์ปจํ…Œ์ด๋„ˆ ์•ˆ cron ์ด 6์‹œ๊ฐ„๋งˆ๋‹ค update-ngxblocker ์‹คํ–‰ โ†’ globalblacklist.conf ๋งŒ ์ตœ์‹ ํ™” ํ›„ nginx -t && nginx -s reload (graceful reload)
  • ๋“ฑ๋ก๋œ cron ๋ผ์ธ:
    0 */6 * * * /usr/local/sbin/update-ngxblocker >> /log/nginx/ngxblocker_YYYYMM.log 2>&1 && nginx -t && nginx -s reload
  • certbot ๊ฐฑ์‹  cron ๊ณผ ๊ฐ™์€ ์ง„์‹ค ์†Œ์Šค(Dockerfile) โ€” ์šด์˜ ์ค‘ ์ถ”๊ฐ€ sudo ์ž‘์—… ๋ถˆํ•„์š”

sample_nginx*.conf ์˜ ํ†ตํ•ฉ ํŒจํ„ด

๊ฐ ๋„๋ฉ”์ธ ์„œ๋ฒ„ ๋ธ”๋ก์˜ ์˜› if ($bad_bot) { return 403; } ์ž๋ฆฌ๋Š” ๋‹ค์Œ 2-line include ๋กœ ๋Œ€์ฒด๋ฉ๋‹ˆ๋‹ค. ngxblocker ์˜ bots.d/ 9๊ฐœ ํŒŒ์ผ ์ค‘ server ์ปจํ…์ŠคํŠธ์— ๋“ค์–ด๊ฐ€๋Š” ๊ฑด ์ด 2๊ฐœ๋ฟ:

include /etc/nginx/bots.d/blockbots.conf;   # server-level `if ($bad_bot)` ๊ฒ€์‚ฌ + 444 return
include /etc/nginx/bots.d/ddos.conf;        # server-level limit_conn / limit_req (๋ด‡๋งŒ ์ ์šฉ)

โš ๏ธ bots.d/{whitelist,blacklist}-*.conf, bots.d/bad-referrer-words.conf, bots.d/custom-bad-referrers.conf ๋Š” server ๋ธ”๋ก์— ์ง์ ‘ include ํ•˜์ง€ ๋ง ๊ฒƒ. ์ด ํŒŒ์ผ๋“ค์€ map/geo ๋ฐ์ดํ„ฐ ํ•ญ๋ชฉ(1.2.3.4 1;, ~*pattern 1;)์„ ๋‹ด๋Š” ํ˜•์‹์ด๋ผ http ์ปจํ…์ŠคํŠธ์˜ map/geo ๋ธ”๋ก ์•ˆ์—์„œ๋งŒ ์œ ํšจํ•˜๋‹ค. server ๋ธ”๋ก์— ๋‘” ์ฑ„ ์šด์˜์ž๊ฐ€ ํ•ญ๋ชฉ ํ•œ ์ค„๋งŒ ์ถ”๊ฐ€ํ•ด๋„ ์ฆ‰์‹œ nginx -t ์‹คํŒจ โ†’ reload ๊ฑฐ๋ถ€ โ†’ ์šด์˜ ๋‹ค์šด. globalblacklist.conf ๊ฐ€ http ์ปจํ…์ŠคํŠธ์—์„œ ์ด 7๊ฐœ ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ include ํ•˜๋ฏ€๋กœ ์šด์˜์ž๋Š” ๊ทธ๋ƒฅ ํ•ด๋‹น ํŒŒ์ผ์— ํ•ญ๋ชฉ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค โ€” server ๋ธ”๋ก ์ˆ˜์ • ๋ถˆํ•„์š”.

์šด์˜์ž๊ฐ€ ์ง์ ‘ ํŽธ์ง‘ํ•˜๋Š” ํŒŒ์ผ (๋„๋ฉ”์ธ๋ณ„ ํ™”์ดํŠธ/๋ธ”๋ž™๋ฆฌ์ŠคํŠธ)

์—…์ŠคํŠธ๋ฆผ์ด ๊ฐฑ์‹ ํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž ์ปค์Šคํ…€ ๋ ˆ์ด์–ด โ€” update-ngxblocker ๊ฐ€ ๋ฎ์–ด์“ฐ์ง€ ์•Š์Œ. ํŒŒ์ผ์— ํ•ญ๋ชฉ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด globalblacklist.conf ๊ฐ€ http ์ปจํ…์ŠคํŠธ์—์„œ ์ž๋™ ํ”ฝ์—…ํ•œ๋‹ค. server ๋ธ”๋ก์€ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Œ:

ํŒŒ์ผ ์šฉ๋„ ํ˜•์‹ ์˜ˆ์‹œ
/etc/nginx/bots.d/whitelist-ips.conf ์ ˆ๋Œ€ ์ฐจ๋‹จํ•˜์ง€ ์•Š์„ IP/CIDR 203.0.113.0/24 0;
/etc/nginx/bots.d/whitelist-domains.conf ์ ˆ๋Œ€ ์ฐจ๋‹จํ•˜์ง€ ์•Š์„ referer ๋„๋ฉ”์ธ ~*example\.com 0;
/etc/nginx/bots.d/blacklist-ips.conf ์ถ”๊ฐ€ ์ฐจ๋‹จ IP/CIDR 198.51.100.5 1;
/etc/nginx/bots.d/blacklist-user-agents.conf ์ถ”๊ฐ€ ์ฐจ๋‹จ User-Agent ~*MyEvilBot 1;
/etc/nginx/bots.d/custom-bad-referrers.conf ์ถ”๊ฐ€ ์ฐจ๋‹จ referrer ํ‚ค์›Œ๋“œ ~*spam\-keyword 1;

โš ๏ธ bots.d/blacklist-domains.conf ๋Š” globalblacklist.conf ๊ฐ€ include ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•ด๋„ ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค. Dockerfile ์ด ๋นˆ ํŒŒ์ผ๋กœ touch ๋งŒ ํ•ด ๋‘˜ ๋ฟ์ด๋‹ค. ์ถ”๊ฐ€ ์ฐจ๋‹จ referer ๋„๋ฉ”์ธ์€ custom-bad-referrers.conf ๋˜๋Š” blacklist-user-agents.conf (UA ๊ธฐ๋ฐ˜) ๋กœ ์ฒ˜๋ฆฌํ•˜๋ผ.

์ˆ˜์ • ํ›„ docker exec -it nginx-<svc>-webserver bash -c "nginx -t && nginx -s reload".

๋™์ž‘ ๊ฒ€์ฆ

# 1) ์•Œ๋ ค์ง„ ๋ด‡ User-Agent ๋กœ ์ฐจ๋‹จ๋˜๋Š”์ง€ ํ™•์ธ
curl -A "MJ12bot" -o /dev/null -s -w "%{http_code}\n" http://localhost/
# โ†’ 444 (๋˜๋Š” 403) ๊ฐ€ ์ •์ƒ

# 2) ์ •์ƒ ๋ธŒ๋ผ์šฐ์ € UA ๋Š” ํ†ต๊ณผ
curl -A "Mozilla/5.0" -o /dev/null -s -w "%{http_code}\n" http://localhost/
# โ†’ 200/3xx/4xx (502/503 ๊ฐ€ ์•„๋‹˜)

# 3) ๊ฐฑ์‹  ๋กœ๊ทธ ํ™•์ธ
docker exec -it nginx-<svc>-webserver tail -20 /log/nginx/ngxblocker_$(date +%Y%m).log

๋กค๋ฐฑ (๊ธด๊ธ‰ ์‹œ)

# 1) ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ globalblacklist.conf ์ผ์‹œ ๋น„ํ™œ์„ฑํ™”
#    (ngxblocker ๋Š” -c /etc/nginx ์˜ต์…˜์œผ๋กœ ์„ค์น˜๋˜์–ด conf.d ๊ฐ€ ์•„๋‹ˆ๋ผ /etc/nginx/ ์ง์†์— ์žˆ์Œ.
#     ๋‹จ์ˆœํžˆ ํŒŒ์ผ์„ ์˜ฎ๊ธฐ๋ฉด nginx.conf ์˜ include ๋ผ์ธ์ด file-not-found ๋กœ nginx -t ์‹คํŒจ โ†’
#     include ๋ผ์ธ์„ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด ๋” ์•ˆ์ „. nginx.conf ๋Š” ํ˜ธ์ŠคํŠธ bind-mount ๋ผ
#     ์ปจํ…Œ์ด๋„ˆ ์•ˆ sed ๊ฐ€ ํ˜ธ์ŠคํŠธ ํŒŒ์ผ๊นŒ์ง€ ๊ฐฑ์‹ ํ•ด ๋‹ค์Œ ๊ธฐ๋™์—์„œ๋„ ๋น„ํ™œ์„ฑ ์œ ์ง€.)
docker exec -it nginx-<svc>-webserver sed -i \
    's|^\(\s*\)include /etc/nginx/globalblacklist.conf|\1# include /etc/nginx/globalblacklist.conf|' \
    /etc/nginx/nginx.conf
docker exec -it nginx-<svc>-webserver bash -c "nginx -t && nginx -s reload"
# ๋ณต๊ท€ ์‹œ: git ์œผ๋กœ nginx.conf ์˜ include ๋ผ์ธ ๋ณต์› โ†’ reload

# 2) ๋˜๋Š” ngxblocker ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ด์ „์˜ ์ •์  bad_bot.conf ๋ฅผ git ์—์„œ ๋ณต์›
#    git log --diff-filter=D -- config/web-server/nginx/gunicorn/conf.d/bad_bot.conf  # ์‚ญ์ œ ์ปค๋ฐ‹ ์‹๋ณ„
#    git show <DELETE_COMMIT>^:config/web-server/nginx/gunicorn/conf.d/bad_bot.conf > config/web-server/nginx/gunicorn/conf.d/bad_bot.conf
#    ๊ทธ ๋‹ค์Œ sample_nginx*.conf ๋ฅผ git revert ๋กœ ๋˜๋Œ๋ฆฌ๊ณ  ์ปจํ…Œ์ด๋„ˆ conf.d ๋กœ ์žฌ๋งˆ์šดํŠธ / reload

4. OS-level ๋™์‹œ ์กฐ์ • ๊ถŒ์žฅ

backlog=2048 (gunicorn/uwsgi/php-fpm) ๊ฐ€ ์‹ค์ œ ํšจ๊ณผ๋ฅผ ๋‚ด๋ ค๋ฉด ์ปค๋„ ํŒŒ๋ผ๋ฏธํ„ฐ๋„ ํ•จ๊ป˜ ์˜ฌ๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค.

# /etc/sysctl.d/99-devspoon-web.conf
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.ip_local_port_range = 10000 65535
vm.overcommit_memory = 1            # Redis ๊ถŒ์žฅ
fs.file-max = 200000

# ์ ์šฉ
sudo sysctl --system
  • ulimit: docker daemon ๋‹จ์—์„œ default-ulimits ๋กœ nofile=65536 ๊ถŒ์žฅ.
  • 8 GB RAM ํ•œ๊ณ„: swap 2-4 GB ํ™•๋ณด(๋ฉ”๋ชจ๋ฆฌ ์ŠคํŒŒ์ดํฌ ์‹œ OOM ๋ฐฉ์–ด์šฉ).

5. ์šด์˜ ์‹œ ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ์ด์Šˆ

์ฆ์ƒ ์˜์‹ฌ ์ง€์  ๋Œ€์‘
์ปจํ…Œ์ด๋„ˆ OOM Kill ์›Œ์ปค ๋ฉ”๋ชจ๋ฆฌ ํ•ฉ > 6 GB docker stats ๋กœ RSS ์ถ”์ด ํ™•์ธ โ†’ workers ๋˜๋Š” max_requests ํ•˜ํ–ฅ
๊ฐ‘์ž‘์Šค๋Ÿฌ์šด ์›Œ์ปค ์žฌ์‹œ์ž‘ max_requests ๋„๋‹ฌ ๋˜๋Š” harakiri/timeout error ๋กœ๊ทธ์—์„œ "Worker timeout" ํ™•์ธ. ์žฅ๊ธฐ ์ž‘์—…์€ celery ๋กœ ๋ถ„๋ฆฌ
502 Bad Gateway ๊ฐ„ํ— upstream ์ข…๋ฃŒ ์‹œ์  vs nginx keepalive gunicorn keepalive ๊ฐ€ nginx keepalive_timeout ๋ณด๋‹ค ์งง์€์ง€ ํ™•์ธ
celery ๋ฉ”๋ชจ๋ฆฌ ์ฆ๊ฐ€ prefork ์›Œ์ปค ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ --max-tasks-per-child=2000 ๋™์ž‘ ํ™•์ธ. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(ํŠนํžˆ numpy/pandas) ์˜ ๋ฉ”๋ชจ๋ฆฌ fragmentaion ๊ฐ€๋Šฅ์„ฑ
logrotate ๋ฏธ๋™์ž‘ ํ˜ธ์ŠคํŠธ ๊ฒฝ๋กœ ์˜คํƒ€ / cron ๋ฏธ์„ค์น˜ script/logrotate (s ์ฃผ์˜) ํด๋”๋ช…, ์ปจํ…Œ์ด๋„ˆ cron ๋ฐ๋ชฌ ๋™์ž‘ ํ™•์ธ
certbot ๊ฐฑ์‹  ์‹คํŒจ webroot ๊ถŒํ•œ / DNS ๋ณ€๊ฒฝ / 80 ํฌํŠธ ์ฐจ๋‹จ /log/nginx/crontab_*.log ํ™•์ธ. ์ˆ˜๋™ dry-run: certbot renew --dry-run
preload_app=True ํ›„ DB ์—๋Ÿฌ fork ํ›„ DB ์ปค๋„ฅ์…˜ ๊ณต์œ  post_fork hook ์—์„œ connections.close_all() (Django) ๋˜๋Š” engine ์žฌ์ƒ์„ฑ

6. ๋ฐฐํฌ ์ ˆ์ฐจ (์ˆ˜๋™ stop/start)

# 1. ์‹ ๊ทœ ์ฝ”๋“œ pull
git pull origin main

# 2. ๋ณ€๊ฒฝ๋œ ๋„์ปคํŒŒ์ผ์ด ์žˆ์œผ๋ฉด ๋นŒ๋“œ (์—†์œผ๋ฉด ์ƒ๋žต)
cd compose/web-service/nginx_<service>
docker compose build --no-cache <service>-app   # ํ•„์š”ํ•œ ์„œ๋น„์Šค๋งŒ

# 3. ์ค‘๋‹จ
docker compose stop

# 4. ์‹œ์ž‘
docker compose --profile celery --profile redis up -d

# 5. ํ—ฌ์Šค ํ™•์ธ
docker compose ps
docker compose logs --tail=100 -f <service>-app

# 6. ์™ธ๋ถ€ ํ—ฌ์Šค์ฒดํฌ
curl -fsS https://<domain>/health || echo "FAIL"

docker compose down ์€ ์‚ฌ์šฉํ•˜์ง€ ๋ง ๊ฒƒ โ€” ์ผ๋ถ€ ๋„คํŠธ์›Œํฌ/๋ณผ๋ฅจ ๋ฉ”ํƒ€๊ฐ€ ๊ฐ™์ด ์ œ๊ฑฐ๋˜์–ด SSL/redis ๋ฐ์ดํ„ฐ ์žฌ๊ตฌ์„ฑ ๋น„์šฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


7. ๋ณด์•ˆ / ์šด์˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • .env ์˜ ๋น„๋ฐ€๊ฐ’ (REDIS_PASSWORD, FLOWER_ID, FLOWER_PWD) ์€ git ์— ์ปค๋ฐ‹ํ•˜์ง€ ์•Š์„ ๊ฒƒ. .gitignore ์˜ **/.env + !**/.env-example ํŒจํ„ด ํ™•์ธ (ยง0.5.1). CELERY_BROKER_URL ์€ .env ์— ์—†์Œ โ€” REDIS_PASSWORD ๋กœ๋ถ€ํ„ฐ compose ๊ฐ€ ํ•ฉ์„ฑ (ยง0.5.3).
  • flower(5555) ํฌํŠธ๋Š” ์™ธ๋ถ€ ๋…ธ์ถœ ์‹œ nginx basic auth ๋˜๋Š” IP allowlist ์ ์šฉ (FLOWER_BASIC_AUTH ๋Š” ์ด๋ฏธ ๊ฐ•์ œ๋˜์–ด ์žˆ์ง€๋งŒ ์ถ”๊ฐ€ ๋ ˆ์ด์–ด ๊ถŒ์žฅ). redis-stats ๋Š” ์ œ๊ฑฐ๋˜์—ˆ์œผ๋ฏ€๋กœ ๋ณ„๋„ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•„์š” ์‹œ RedisInsight/redis_exporter ๋„์ž… (ยง0.5.7).
  • redis ๋Š” ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ ์ „์šฉ(์™ธ๋ถ€ ํฌํŠธ ๋ฏธ๋…ธ์ถœ ์ƒํƒœ๊ฐ€ ๊ธฐ๋ณธ โ€” ์œ ์ง€ ๊ถŒ์žฅ). protected-mode yes + requirepass ๊ฐ€ ๊ฐ•์ œ๋˜์–ด ๋™์ผ ๋„คํŠธ์›Œํฌ ์ปจํ…Œ์ด๋„ˆ๋„ ์ธ์ฆ ํ•„์š” (ยง0.5.2).
  • Python ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€ ๋นŒ๋“œ ์‹œ docker build --build-arg PYTHON_SHA256=<official> ๋˜๋Š” ARG default ๊ฐ’(docker/gunicorn/Dockerfile) ์ด python.org ๊ณต์‹ ํ•ด์‹œ์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ๋ถ„๊ธฐ 1ํšŒ ์žฌํ™•์ธ (ยง0.5.4).
  • docker/nginx/Dockerfile ์˜ cron ์‹œ๊ฐ„(๋งค์ฃผ ์›”์š”์ผ 05:00)์ด ํŠธ๋ž˜ํ”ฝ ํ•œ์‚ฐ ์‹œ๊ฐ„๋Œ€์ธ์ง€ ์šด์˜ ํ™˜๊ฒฝ ๊ธฐ์ค€์œผ๋กœ ์žฌ๊ฒ€ํ† .
  • ๋กœ๊ทธ ๋””์Šคํฌ ๋ชจ๋‹ˆํ„ฐ๋ง โ€” df -h log/ ๊ฐ€ 80% ๋„๋‹ฌ ์‹œ ์•Œ๋ฆผ.
  • ufw / ํด๋ผ์šฐ๋“œ ๋ฐฉํ™”๋ฒฝ์—์„œ 80/tcp, 443/tcp ๋งŒ ์™ธ๋ถ€ ๋…ธ์ถœ, ๊ทธ ์™ธ ๋ชจ๋“  ํฌํŠธ ์ฐจ๋‹จ (flower 5555 ๋Š” ๋‚ด๋ถ€๋ง์—์„œ๋งŒ).
  • OS ์‹œ๊ฐ„ ๋™๊ธฐํ™”(chrony ๋˜๋Š” systemd-timesyncd) โ€” certbot/cron/๋กœ๊ทธ ํƒ€์ž„์Šคํƒฌํ”„ ์ •ํ•ฉ์„ฑ.

8. Python ์˜์กด์„ฑ / uv ์ •์ฑ… โ€” "์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ๋Š” ๊ฐ€์ƒํ™˜๊ฒฝ ๋ฏธ์‚ฌ์šฉ"

๋ณธ ํ”„๋กœ์ ํŠธ๋Š” www/django_sample ์˜ Python ์˜์กด์„ฑ์„ uv ๋กœ ๊ด€๋ฆฌํ•˜์ง€๋งŒ, ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ๋Š” ์˜๋„์ ์œผ๋กœ ๋ณ„๋„ ๊ฐ€์ƒํ™˜๊ฒฝ(.venv)์„ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆ ์ž์ฒด๊ฐ€ ๊ฒฉ๋ฆฌ ๋‹จ์œ„์ด๋ฏ€๋กœ venv ๋Š” ๋ถˆํ•„์š”ํ•œ ์ค‘๋ณต ๊ณ„์ธต์ด๋ฉฐ, ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์„ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๋™์ž‘ ์›๋ฆฌ

  • docker/gunicorn/Dockerfile, docker/uwsgi/Dockerfile ์— ๋‹ค์Œ ENV ๊ฐ€ ๋ฐ•ํ˜€ ์žˆ์Œ:
    UV_PROJECT_ENVIRONMENT=/usr/local
    UV_LINK_MODE=copy
    UV_COMPILE_BYTECODE=1
    UV_NO_CACHE=1
    
  • ์ด๋กœ ์ธํ•ด ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ์˜ uv sync ๋Š” .venv ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๊ณ  /usr/local/lib/python3.14/site-packages (์‹œ์Šคํ…œ Python) ์— ์ง์ ‘ ์„ค์น˜.
  • compose command: ๋Š” uv run ์„ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ์‹œ์Šคํ…œ ๋ฐ”์ด๋„ˆ๋ฆฌ(gunicorn, daphne, uwsgi, celery) ๋ฅผ ๊ทธ๋Œ€๋กœ ํ˜ธ์ถœ.
  • --inexact ํ”Œ๋ž˜๊ทธ๋กœ Dockerfile ์ด ์‚ฌ์ „ ์„ค์น˜ํ•œ ํŒจํ‚ค์ง€(fastapi/sqlalchemy/wheel ๋“ฑ) ๊ฐ€ ์ œ๊ฑฐ๋˜์ง€ ์•Š๋„๋ก ๋ณดํ˜ธ.

์šด์˜์ž๊ฐ€ ์–ป๋Š” ์ด์ 

ํ•ญ๋ชฉ venv ์‚ฌ์šฉ ์‹œ ๋ณธ ํ”„๋กœ์ ํŠธ (์‹œ์Šคํ…œ ์„ค์น˜)
import ๋””๋ฒ„๊ทธ uv run python -c "import django" python -c "import django"
ํŒจํ‚ค์ง€ ๋ชฉ๋ก uv pip list --python .venv/bin/python pip list
ํ˜ธ์ŠคํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ www/<project>/.venv/ ๊ฐ€ ํ˜ธ์ŠคํŠธ์— ์ƒ์„ฑ๋จ ํ˜ธ์ŠคํŠธ๋Š” ์†Œ์Šค๋งŒ, ๊นจ๋—ํ•˜๊ฒŒ ์œ ์ง€
cross-volume hardlink ์ข…์ข… ์ถฉ๋Œ โ†’ UV_LINK_MODE=copy ์šฐํšŒ ํ•„์š” ๋™์ผ layer ์•ˆ์ด๋ผ ๋ฌด์˜ํ–ฅ
ํ•œ ์ปจํ…Œ์ด๋„ˆ์— ๋‘ venv ๊ฐ€๋Šฅ, ํ˜ผ๋ž€ ๋‹จ์ผ ์‹œ์Šคํ…œ site-packages โ†’ ๋ชจํ˜ธํ•จ ์—†์Œ

๊ฐœ๋ฐœ ๋จธ์‹ (ํ˜ธ์ŠคํŠธ) ์—์„œ๋Š” ์ •๋ฐ˜๋Œ€

UV_PROJECT_ENVIRONMENT ๊ฐ€ ํ˜ธ์ŠคํŠธ์—๋Š” ์—†์œผ๋ฏ€๋กœ, ๊ฐœ๋ฐœ์ž๋Š” cd www/django_sample && uv sync ๋งŒ์œผ๋กœ ์ž๋™์œผ๋กœ .venv ๊ฐ€ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค. ํ˜ธ์ŠคํŠธ์™€ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๊ฐ™์€ pyproject.toml ์„ ์“ฐ์ง€๋งŒ ์„ค์น˜ ์œ„์น˜๋งŒ ๋‹ค๋ฅด๊ฒŒ ๊ฐ€์ ธ๊ฐ‘๋‹ˆ๋‹ค.

์˜์กด์„ฑ ์ถ”๊ฐ€/๊ฐฑ์‹  ์›Œํฌํ”Œ๋กœ์šฐ

# ํ˜ธ์ŠคํŠธ(๊ฐœ๋ฐœ ๋จธ์‹ )์—์„œ:
cd www/django_sample
uv add django-celery-beat            # ๋Ÿฐํƒ€์ž„ deps ์ถ”๊ฐ€ โ†’ pyproject.toml + uv.lock ๊ฐฑ์‹ 
uv add --dev pytest-mock             # ๊ฐœ๋ฐœ deps ์ถ”๊ฐ€
uv lock                              # ๋ฝ๋งŒ ์žฌ์ƒ์„ฑ (ํ•„์š” ์‹œ)

# ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ปค๋ฐ‹:
git add pyproject.toml uv.lock
git commit -m "deps: add django-celery-beat"

# ์ปจํ…Œ์ด๋„ˆ ์žฌ๊ธฐ๋™ โ†’ ์‹œ์ž‘ ์‹œ์ ์— uv sync ๊ฐ€ ์ž๋™ ์‹คํ–‰๋˜์–ด ์‹œ์Šคํ…œ Python ์— ๋ฐ˜์˜:
docker compose stop && docker compose up -d

ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ์˜ˆ์‹œ

# 1) ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ ํŒจํ‚ค์ง€ ์„ค์น˜ ์ƒํƒœ ํ™•์ธ โ€” venv ํ™œ์„ฑํ™” ๋ถˆํ•„์š”
docker exec -it gunicorn-app pip list | grep -i django

# 2) Django ํ™˜๊ฒฝ์—์„œ ์ฆ‰์‹œ ORM ์…ธ ์ง„์ž…
docker exec -it gunicorn-app python manage.py shell

# 3) uv sync ๊ฐ€ ์‹ค์ œ๋กœ ์–ด๋””์— ์„ค์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
docker exec -it gunicorn-app uv pip list --system
docker exec -it gunicorn-app python -c "import django; print(django.__file__)"
# โ†’ /usr/local/lib/python3.14/site-packages/django/__init__.py

์ฃผ์˜ โ€” extras race ๋ฐฉ์ง€ ์›์น™ ์œ ์ง€

๊ฐ™์€ compose ์Šคํƒ์˜ ๋ชจ๋“  ํŒŒ์ด์ฌ ์ปจํ…Œ์ด๋„ˆ(app + celery + celerybeat) ๋Š” ๋ฐ˜๋“œ์‹œ ๋™์ผํ•œ extras ์กฐํ•ฉ ์œผ๋กœ uv sync ํ•ฉ๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ site-packages ๊ฐ€ ๊ณต์œ ๋˜๋ฏ€๋กœ, ํ•œ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋‹ค๋ฅธ extras ๋กœ sync ํ•˜๋ฉด ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ์˜ ํŒจํ‚ค์ง€๊ฐ€ ์ œ๊ฑฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --inexact ๊ฐ€ 1์ฐจ ์•ˆ์ „๋ง์ด์ง€๋งŒ extras ์กฐํ•ฉ์€ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”.


9. ๋ณ€๊ฒฝ ์‹œ ์˜ํ–ฅ ๋ฒ”์œ„ ๋งคํŠธ๋ฆญ์Šค

๋ณ€๊ฒฝ ์žฌ๋นŒ๋“œ ํ•„์š”? ์ปจํ…Œ์ด๋„ˆ ์žฌ๊ธฐ๋™ ํ•„์š”?
docker/<service>/Dockerfile โœ… docker compose build โœ…
config/app-server/*/... (conf.py, ini) โŒ (๋ณผ๋ฅจ ๋งˆ์šดํŠธ) โœ… (์›Œ์ปค ์žฌ๋กœ๋“œ)
config/web-server/nginx/... (๋ณ„๋„ ๊ด€๋ฆฌ) โŒ nginx ์ปจํ…Œ์ด๋„ˆ๋งŒ nginx -s reload
compose/.../docker-compose.yml ๋ณ€๊ฒฝ ์ข…๋ฅ˜์— ๋”ฐ๋ผ โœ…
script/logrotate/* โŒ โŒ (๋‹ค์Œ cron tick ๋ถ€ํ„ฐ ์ ์šฉ)
script/letsencrypt.sh โŒ โŒ
script/test/* โŒ โŒ (ํ˜ธ์ŠคํŠธ ์ˆ˜๋™ ์‹คํ–‰ ๊ฒ€์ฆ ์ž์‚ฐ, ์šด์˜ ๋ฌด๊ด€)

10. ํ…Œ์ŠคํŠธ / ๊ฒ€์ฆ ์ธํ”„๋ผ (script/test/)

script/test/ ์˜ ์Šคํฌ๋ฆฝํŠธ๋Š” ์šด์˜ ์ด๋ฏธ์ง€/์Šคํƒ๊ณผ ๋ฌด๊ด€ํ•ฉ๋‹ˆ๋‹ค. Dockerfile / docker-compose ์–ด๋А ๊ณณ์—์„œ๋„ ์ฐธ์กฐ๋˜์ง€ ์•Š์œผ๋ฉฐ ์ปจํ…Œ์ด๋„ˆ ์•ˆ์— ๋“ค์–ด๊ฐ€์ง€๋„ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ํ˜ธ์ŠคํŠธ(WSL2 Ubuntu + Docker ๊ฐ€์ •)์—์„œ ์ง์ ‘ ์‹คํ–‰ํ•ด ์ •ํ•ฉ์„ฑ๊ณผ ํšŒ๊ท€๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ์ž์‚ฐ์ž…๋‹ˆ๋‹ค. ํด๋”๋ฅผ ์‚ญ์ œํ•ด๋„ ์šด์˜ ์„œ๋น„์Šค ๋™์ž‘์—๋Š” ์˜ํ–ฅ์ด ์—†์Šต๋‹ˆ๋‹ค โ€” ํšŒ๊ท€ ๊ฒ€์ฆ ํŽธ์˜ ์ž์‚ฐ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

๊ตฌ์„ฑ (2๊ฐœ ํŒŒ์ผ)

ํŒŒ์ผ ์ข…๋ฅ˜ ์†Œ์š” ์ปจํ…Œ์ด๋„ˆ ๋ณ€๊ฒฝ?
preflight.sh ํ™˜๊ฒฝ ์‚ฌ์ „ ์ ๊ฒ€ (read-only) ~30 ์ดˆ ์—†์Œ
verify-ngxblocker.sh ngxblocker ์ข…๋‹จ๊ฐ„ ์ž๋™ ๊ฒ€์ฆ 1โ€“3 ๋ถ„ gunicorn ์Šคํƒ์„ down โ†’ up + ์ž„์‹œ conf ์ถ”๊ฐ€/์ œ๊ฑฐ (์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ž์ฒด cleanup)

10.1 preflight.sh โ€” ์ƒˆ ํ™˜๊ฒฝ ์…‹์—… ์‹œ ์‚ฌ์ „ ์ ๊ฒ€

ํ…Œ์ŠคํŠธ ๋˜๋Š” ์šด์˜ ์…‹์—… ์‹œ์ž‘ ์ „ ํ˜ธ์ŠคํŠธ ํ™˜๊ฒฝ์ด ์ž‘์—… ์š”๊ฑด์„ ๋งŒ์กฑํ•˜๋Š”์ง€๋ฅผ 30 ์ดˆ ์•ˆ์— ํ™•์ธํ•˜๋Š” read-only ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค. ์–ด๋–ค ํŒŒ์ผ๋„ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ , ์ปจํ…Œ์ด๋„ˆ๋„ ๋„์šฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

cd /path/to/devspoon-web     # ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋ฃจํŠธ
bash script/test/preflight.sh

์ข…๋ฃŒ ์ฝ”๋“œ: 0 = PREFLIGHT PASS(ํ…Œ์ŠคํŠธ/๋ฐฐํฌ ์‹œ์ž‘ ๊ฐ€๋Šฅ), 1 = PREFLIGHT FAIL(๋ฏธ์ถฉ์กฑ ํ•ญ๋ชฉ ์กด์žฌ โ€” ํ™”๋ฉด์˜ [MISS] ๋ผ์ธ ์ˆ˜์ • ํ›„ ์žฌ์‹คํ–‰).

์–ธ์ œ ์‹คํ–‰ํ•˜๋Š”๊ฐ€

  1. ์ƒˆ dev ํ™˜๊ฒฝ(WSL2 / ํด๋ผ์šฐ๋“œ VM) ์…‹์—… ์งํ›„ โ€” ํ•„์ˆ˜ ๋„๊ตฌ๊ฐ€ ๋น ์ง€์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธ.
  2. OS / Docker ๋ฉ”์ด์ € ์—…๋ฐ์ดํŠธ ์งํ›„ โ€” docker compose v2 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ™์€ ํšŒ๊ท€ ์ ๊ฒ€.
  3. ์‹ ๊ทœ ํŒ€์› ์˜จ๋ณด๋”ฉ ์ฒซ 30 ๋ถ„ โ€” "์™œ ์•ˆ ๋ผ์š”?" ๋ผ์šด๋“œํŠธ๋ฆฝ์„ ์ค„์ž„.
  4. CI ์›Œํฌํ”Œ๋กœ์˜ ์ฒซ step ์œผ๋กœ ํ˜ธ์ถœ โ€” ์˜์กด์„ฑ ๊ฐ€์‹œํ™” (์ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํ™˜๊ฒฝ ๋ฌธ์„œ ์—ญํ• ).

๋ฌด์—‡์„ ํ™•์ธํ•˜๋Š”๊ฐ€ (4๊ฐœ ์นดํ…Œ๊ณ ๋ฆฌ)

์นดํ…Œ๊ณ ๋ฆฌ ํ•ญ๋ชฉ ์˜ˆ์‹œ ์‹คํŒจ ์‹œ
[1] Required tools docker(>=24), docker compose(v2), jq, curl, openssl, wrk(optional) [MISS] โ€” ํ•ด๋‹น ๋„๊ตฌ ์„ค์น˜ ํ•„์š”
[2] Repository files 5๊ฐœ ์Šคํƒ ๊ฐ .env(gunicorn / uvicorn / daphne / uwsgi / nginx_php-7.3 / nginx_php-8.4), Dockerfile 4์ข…(php-fpm ์€ 7.3/8.4 ๋‘˜ ๋‹ค), entrypoint, pyproject.toml, script/letsencrypt.sh [MISS] โ€” ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋ฌด๊ฒฐ์„ฑ ๊นจ์ง. clone ์žฌ์‹œ๋„ ๋˜๋Š” git status ํ™•์ธ
[3] Design invariants script/logrotate ํด๋”๋ช… (์˜คํƒ€ 'loglotate' ์•„๋‹˜), log/.gitkeep ร— 11, pyproject.toml PEP 621 ์—ฌ๋ถ€, UV_PROJECT_ENVIRONMENT=/usr/local, FROM ๋ฒ ์ด์Šค ์ •ํ•ฉ์„ฑ, service nginx restart ํšŒ๊ท€ ๋ถ€์žฌ, uwsgi.ini py-autoreload=0 ๋“ฑ [MISS] โ€” ์˜๋„๋œ ๋””์ž์ธ ๊ฒฐ์ • ๊นจ์ง. ์ž์„ธํ•œ ๊ทผ๊ฑฐ๋Š” ยง0, ยง6, ยง8 ์ฐธ์กฐ
[4] Host environment WSL2 ์—ฌ๋ถ€, ๋””์Šคํฌ ์—ฌ์œ  20 GB+, net.core.somaxconn 4096+ [WARN] โ€” informational. ์šด์˜ ์‹œ ๊ถŒ์žฅ์ด์ง€๋งŒ dev ์—์„œ๋Š” ๋ฌด์‹œ ๊ฐ€๋Šฅ

[3] ์˜ service nginx restart ๊ฒ€์‚ฌ๋Š” ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ service ๋ช…๋ น์œผ๋กœ nginx ๋ฅผ restart ํ•˜๋ฉด PID 1 = nginx ์ธ ํ™˜๊ฒฝ์—์„œ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ํ†ต์งธ๋กœ ์ข…๋ฃŒ๋˜๋Š” ์‚ฌ๊ณ ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์ •์  grep ์ž…๋‹ˆ๋‹ค.


10.2 verify-ngxblocker.sh โ€” nginx ๋ด‡ ์ฐจ๋‹จ ์ข…๋‹จ๊ฐ„ ๊ฒ€์ฆ

nginx-ultimate-bad-bot-blocker ์˜ ๋‹ค์šด๋กœ๋“œ, ํ†ตํ•ฉ, ์ฐจ๋‹จ ๋™์ž‘์ด ์‹ค์ œ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€๋ฅผ ์ข…๋‹จ๊ฐ„์œผ๋กœ ์ž๋™ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. gunicorn ์Šคํƒ(compose/web-service/nginx_gunicorn)์„ ํ…Œ์ŠคํŠธ ๋ฒ ๋“œ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค โ€” PHP ์Šคํƒ์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์–ด์ฐจํ”ผ nginx ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€๋Š” ๋ชจ๋‘ ๋™์ผํ•˜๋ฏ€๋กœ ํ•œ ์Šคํƒ์—์„œ๋งŒ ๊ฒ€์ฆํ•ด๋„ ์ถฉ๋ถ„).

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

cd /path/to/devspoon-web     # ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋ฃจํŠธ
bash script/test/verify-ngxblocker.sh

์ข…๋ฃŒ ์ฝ”๋“œ: 0 = ALL CHECKS PASSED, 1 = ํ•œ ๊ฐœ ์ด์ƒ ์‹คํŒจ(ํ™”๋ฉด์˜ [FAIL] ๋ผ์ธ ํ™•์ธ). PASS ์‹œ ๋งˆ์ง€๋ง‰ ์ค„์— ๋…น์ƒ‰ ALL CHECKS PASSED ๊ฐ€ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.

์ค‘์š”ํ•œ ๋ถ€์ž‘์šฉ โ€” ์‹คํ–‰ ์งํ›„ ์ƒํƒœ

์ด ์Šคํฌ๋ฆฝํŠธ๋Š” read-only ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ๋‹ค์Œ ์ˆœ์„œ๋กœ ํ™˜๊ฒฝ์„ ๋งŒ์ง‘๋‹ˆ๋‹ค:

  1. gunicorn ์Šคํƒ์„ docker compose down -v --remove-orphans ํ›„ up -d webserver redis โ€” ๊ฒ€์ฆ์„ ์œ„ํ•ด ๊นจ๋—ํ•œ ์ƒํƒœ์—์„œ ์‹œ์ž‘. ๊ธฐ์กด์— ๋‹ค๋ฅธ gunicorn ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋–  ์žˆ์—ˆ๋‹ค๋ฉด ์ข…๋ฃŒ๋˜๋ฉฐ, ๋ณผ๋ฅจ๋„ ํ•จ๊ป˜ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.
  2. ์ปจํ…Œ์ด๋„ˆ ์•ˆ์— ์ž„์‹œ conf (/etc/nginx/conf.d/zz_blocker_test.conf) ์ถ”๊ฐ€ โ€” Host: blocker.test ๋งค์นญ server ๋ธ”๋ก. ์Šคํฌ๋ฆฝํŠธ ์ข…๋ฃŒ ์ง์ „์— Cleanup ๋‹จ๊ณ„์—์„œ ์ œ๊ฑฐ + reload ํ•ฉ๋‹ˆ๋‹ค.
  3. ์ž„์‹œ ๋กœ๊ทธ ํŒŒ์ผ (/log/nginx/blocker_test_access.log, blocker_test_error.log) โ€” ํ˜ธ์ŠคํŠธ ๋ณผ๋ฅจ์— ๋‚จ์Šต๋‹ˆ๋‹ค (ํ•„์š” ์‹œ ์ˆ˜๋™ ์ •๋ฆฌ).
  4. ์ˆ˜๋™ update-ngxblocker -c /etc/nginx ํ˜ธ์ถœ โ€” globalblacklist.conf ๋ฅผ ์ตœ์‹ ํ™”ํ•˜๋ฉฐ mtime ์ด ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค.

์šด์˜ ์ค‘์ธ ํ˜ธ์ŠคํŠธ์—์„œ ์ง์ ‘ ๋Œ๋ฆฌ๋ฉด ์ผ์‹œ์ ์œผ๋กœ gunicorn ์Šคํƒ ๋‹ค์šดํƒ€์ž„์ด ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ์šด์˜ ํŠธ๋ž˜ํ”ฝ์ด ์žˆ๋Š” ์„œ๋ฒ„์—์„œ๋Š” ์ •๋น„ ์‹œ๊ฐ„๋Œ€์—๋งŒ ์‹คํ–‰ํ•˜์„ธ์š”.

์–ธ์ œ ์‹คํ–‰ํ•˜๋Š”๊ฐ€

ํŠธ๋ฆฌ๊ฑฐ ์ด์œ 
nginx.conf ์˜ limit_conn_zone / limit_req_zone($bot_iplimit) ๊ฐ™์€ zone ์ด๋ฆ„ยทkeyยทsizeยทrate ๋ณ€๊ฒฝ rate-limit ์ •์ฑ…์ด ๋ด‡ ์ฐจ๋‹จ ํ†ตํ•ฉ์— ํšŒ๊ท€๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Œ
bots.d/ddos.conf ๋˜๋Š” globalblacklist.conf ์˜ ์ˆ˜๋™ update-ngxblocker ์‹คํ–‰ ์งํ›„ ์ž๋™ cron(6์‹œ๊ฐ„) ์™ธ ์ˆ˜๋™ ๊ฐฑ์‹ ์€ ํšŒ๊ท€ ๊ฐ€๋Šฅ์„ฑ์ด ํผ
sample_nginx*.conf ์˜ ngxblocker include ๋ผ์ธ(blockbots.conf / ddos.conf) ์œ„์น˜ ๋ณ€๊ฒฝ ๋˜๋Š” ๋‹ค๋ฅธ bots.d/* ํŒŒ์ผ ์ถ”๊ฐ€ server ์ปจํ…์ŠคํŠธ include ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ๋“ค์–ด๊ฐ”๋Š”์ง€ ํ™•์ธ
docker/nginx/Dockerfile ์žฌ๋นŒ๋“œ (install-ngxblocker, ca-certificates, cron ์…‹์—… ๋“ฑ ๋‹จ๊ณ„ ๋ณ€๊ฒฝ) ๋นŒ๋“œ ์‹œ์  bake-in ์‚ฐ์ถœ๋ฌผ์ด ๋ชจ๋‘ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
6 ๊ฐœ์›” ์ด์ƒ ๋นŒ๋“œ/๊ฒ€์ฆ ๊ณต๋ฐฑ ํ›„ ์žฌ๊ฐ€๋™ ์—…์ŠคํŠธ๋ฆผ(mitchellkrogza/nginx-ultimate-bad-bot-blocker) ์˜ ํฌ๋งท์ด ๋ฏธ์„ธํ•˜๊ฒŒ ๋ฐ”๋€Œ์–ด grep ํŒจํ„ด์ด ๊นจ์งˆ ์ˆ˜ ์žˆ์Œ

๊ฒ€์ฆ ๋‹จ๊ณ„ (Step Aโ€“E)

Step ๋ฌด์—‡์„ ๋ณด๋Š”๊ฐ€
A ์Šคํƒ ๊ธฐ๋™ docker compose down -v โ†’ up -d webserver redis, nginx -t ํ†ต๊ณผ
B ๋‹ค์šด๋กœ๋“œ ์‚ฐ์ถœ๋ฌผ globalblacklist.conf โ‰ฅ 400 KB, 1000+ ๋ด‡ regex ํŒจํ„ด, ์•Œ๋ ค์ง„ ๋ด‡ 8 ์ข…(MJ12, Ahrefs, Semrush, DotBot, BLEX, Scrapy, nikto, sqlmap) ํฌํ•จ, bots.d/ 9 ํŒŒ์ผ ๋ชจ๋‘ ์กด์žฌ
C nginx ํ†ตํ•ฉ nginx.conf ๊ฐ€ globalblacklist.conf ๋ฅผ 1ํšŒ include, $bad_bot ๋ณ€์ˆ˜ ์ •์˜, nginx -t syntax/test OK, master + worker โ‰ฅ 2
D cron / ๊ฐฑ์‹  crontab ์— update-ngxblocker -c /etc/nginx ๋ผ์ธ ๋“ฑ๋ก, cron ๋ฐ๋ชฌ ์‹คํ–‰, ์ˆ˜๋™ update ์‹คํ–‰ ํ›„ ํŒŒ์ผ ์ •์ƒ + reload ํ›„ ์›Œ์ปค ์ •์ƒ
E ์ข…๋‹จ๊ฐ„ ์ฐจ๋‹จ ์ž„์‹œ server ๋ธ”๋ก(blocker.test) ์ถ”๊ฐ€ ํ›„ โ€” Mozilla UA โ†’ 200, ๋ด‡ UA 4์ข…(MJ12 / Ahrefs / Semrush / BLEX) โ†’ 444 / 000 / 403, bad referer(semalt.com) ์ฐจ๋‹จ(์˜ต์…˜), ์•ก์„ธ์Šค ๋กœ๊ทธ ๊ธฐ๋ก

E-4 ์˜ ๋ด‡ UA ์‘๋‹ต ์ฝ”๋“œ๊ฐ€ 444 ๊ฐ€ ์•„๋‹ˆ๋ผ 000 ์œผ๋กœ ๋ณด์ด๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด๋Š” nginx ๊ฐ€ ์‘๋‹ต ์—†์ด ์—ฐ๊ฒฐ์„ ๋‹ซ์•„ curl ์ด ์‘๋‹ต์„ ๋ชป ๋ฐ›์•˜๋‹ค๋Š” ์˜๋ฏธ๋กœ ๋™์ผํ•œ PASS ์ž…๋‹ˆ๋‹ค.


์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๋น ๋ฅธ ๊ฐ€์ด๋“œ

์‹œ๋‚˜๋ฆฌ์˜ค ์‹คํ–‰ ๋ช…๋ น
์ƒˆ dev ํ™˜๊ฒฝ ์…‹์—… ํ›„ ์ฒซ ์ง„๋‹จ bash script/test/preflight.sh
OS / Docker ์—…๋ฐ์ดํŠธ ์งํ›„ ํšŒ๊ท€ ์ ๊ฒ€ bash script/test/preflight.sh
nginx.conf ์˜ rate-limit / bots.d/* ๋ณ€๊ฒฝ ํ›„ bash script/test/verify-ngxblocker.sh
docker/nginx/Dockerfile ์žฌ๋นŒ๋“œ ํ›„ bash script/test/verify-ngxblocker.sh
๋‘ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์—ฐ๋‹ฌ์•„ (์…‹์—… โ†’ ๋ด‡ ์ฐจ๋‹จ ๊ฒ€์ฆ) bash script/test/preflight.sh && bash script/test/verify-ngxblocker.sh

์ƒˆ๋กœ์šด ํšŒ๊ท€ ๊ฒ€์ฆ ์ž์‚ฐ์ด ํ•„์š”ํ•ด์ง€๋ฉด ๊ฐ™์€ ํด๋”์— verify-<topic>.sh ๋˜๋Š” preflight-<topic>.sh ๋„ค์ด๋ฐ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋ฉด ์ผ๊ด€์„ฑ์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.


10.3 script/test_run/ โ€” ๋‹จ๊ณ„ํ™”๋œ ํšŒ๊ท€ ๊ฒ€์ฆ ๋ฐฐํ„ฐ๋ฆฌ (aisum-infrakit ๋„์ž…)

ยง10 ์˜ script/test/ ๊ฐ€ "ํŠน์ • ์˜์—ญ๋งŒ ์‹ ์† ๊ฒ€์ฆ" ์ธ ๋ฐ ๋น„ํ•ด, script/test_run/ ์€ ๋‹จ๊ณ„ ๋ฒˆํ˜ธ (s0/s1b/s2/s3/s5/s6) ๋กœ ์ •๋ ฌ๋œ ์ข…๋‹จ๊ฐ„ ํšŒ๊ท€ ๋ฐฐํ„ฐ๋ฆฌ ์ž…๋‹ˆ๋‹ค. aisum-infrakit ์˜ ๊ฒ€์ฆ ์ž์‚ฐ์„ ๋ณธ ํ”„๋กœ์ ํŠธ๋กœ ์—ญ์ด์‹ํ•˜์—ฌ ๋™์ผํ•œ ํšŒ๊ท€ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ devspoon-web ์—์„œ๋„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ตฌ์„ฑ

๋‹จ๊ณ„ ์Šคํฌ๋ฆฝํŠธ ๊ฒ€์ฆ ์˜์—ญ ๋น„๊ณ 
s0 s0_prereq.sh docker / docker compose ๋ฒ„์ „, ํ˜ธ์ŠคํŠธ ํฌํŠธ(80/443/5555) ๊ฐ€์šฉ, log/<service>/ ์กด์žฌ, uv ์„ค์น˜, www/django_sample uv sync ์‚ฌ์ „ ์ ๊ฒ€ (read-only ๊ฐ€๊นŒ์›€)
s1b s1b_exit_check.sh ์ปจํ…Œ์ด๋„ˆ ๋น„์ •์ƒ ์ข…๋ฃŒ ์‹œ exit code / ๋กœ๊ทธ ํŒจํ„ด ๊ฒ€์‚ฌ ์ง„๋‹จ์šฉ
s1b s1b_nginx_conf_generators.sh 5๊ฐœ ์Šคํƒ(gunicorn / uvicorn / uwsgi / php-7.3 / php-8.4) ์˜ nginx_http_conf.sh / nginx_https_conf.sh ๊ฐ€ ์ •์ƒ ์‚ฐ์ถœ๋ฌผ์„ ๋งŒ๋“œ๋Š”์ง€ โ€” ์น˜ํ™˜ ๋ˆ„๋ฝ, ๋น„์–ด ์žˆ๋Š” placeholder, ํŒŒ์ผ ๊ถŒํ•œ ๊ฒ€์‚ฌ conf ์ƒ์„ฑ๊ธฐ ํšŒ๊ท€
s2 s2_build.sh ์Šคํƒ๋ณ„ Dockerfile ์„ docker build -f ๋กœ ๊ฒฉ๋ฆฌ ๋นŒ๋“œ (php-fpm ์€ 7.3/8.4 ๋‘ ๋ณ€ํ˜• ๋ชจ๋‘), aisum-test/* ํƒœ๊ทธ๋กœ ์‚ฐ์ถœ ๋นŒ๋“œ ํšŒ๊ท€. compose layer ์™€ ๋ฌด๊ด€
s2a s2a_image_inspect.sh ๋นŒ๋“œ๋œ ์ด๋ฏธ์ง€์˜ baselayer ์ •ํ•ฉ, ENV / WORKDIR / CMD ๊ฒ€์‚ฌ ์ด๋ฏธ์ง€ ๋ฉ”ํƒ€
s3 s3_stack_smoke.sh <stack> <appname> <appcontainer> <stack_name> ๋‹จ์ผ ์Šคํƒ ๊ธฐ๋™ โ†’ nginx -t โ†’ curl -H "Host: ..." ์‘๋‹ต 200 ์—ฌ๋ถ€ โ†’ cleanup per-stack smoke
s5 s5_https.sh <stack> HTTPS ์ธก: dhparam ์ƒ์„ฑ/๋งˆ์šดํŠธ/๋ณต์›, self-signed cert ์ƒ์„ฑ, sample_nginx_https.conf ์น˜ํ™˜ ์‚ฐ์ถœ๋ฌผ ๊ฒ€์ฆ, nginx -t ํ†ต๊ณผ โ€” certbot ๋ฐœ๊ธ‰์€ ์ œ์™ธ (๋„๋ฉ”์ธ ์—†๋Š” ํ™˜๊ฒฝ ๊ฐ€์ •) HTTPS ์ •ํ•ฉ์„ฑ
s6 s6_regression.sh ํ†ตํ•ฉ ํšŒ๊ท€ โ€” ์œ„ ๋ชจ๋“  ๋‹จ๊ณ„๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ํ˜ธ์ถœ ํ›„ ์ข…ํ•ฉ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ์•ผ๊ฐ„ ํšŒ๊ท€
๋ณด์กฐ ssl_diag.sh dhparam ๊ฒฝ๋กœ/๋‚ด์šฉ ๊ฒ€์‚ฌ + ํ˜ธ์ŠคํŠธ ๋ฐฑ์—…๋ณธ โ†” ์ปจํ…Œ์ด๋„ˆ ๋ณธ ์ผ์น˜ ๊ฒ€์‚ฌ ยง3 dhparam ์˜์†ํ™” ์ ˆ์˜ ์ž๋™ํ™” ๊ฒ€์ฆ
๋ณด์กฐ verify_block.sh ๋ด‡/์Šค์บ๋„ˆ ์ฐจ๋‹จ ๋™์ž‘ ๊ฒ€์‚ฌ (ยง10.2 ์˜ verify-ngxblocker ์™€ ์˜์—ญ ์ผ๋ถ€ ์ค‘๋ณต)
๋ณด์กฐ verify_compose_yml.sh 6 ์Šคํƒ docker-compose.yml ์˜ dhparam ๋งˆ์šดํŠธ / ์•ˆํ‹ฐํŒจํ„ด (ssl/certs ๋งˆ์šดํŠธ, ulimits ๋ฏธ์ •์˜) ์ •์  ๊ฒ€์‚ฌ ์ •์  ํšŒ๊ท€
๋ณด์กฐ verify_conf_generators.sh 4 ์Šคํƒ ร— HTTP+HTTPS generator ์‚ฐ์ถœ๋ฌผ ๊ฒ€์ฆ ์ •์  ํšŒ๊ท€
๋ณด์กฐ verify_dhparam_lifecycle.sh / verify_dhparam_host_wins.sh dhparam A/B/C ๋‹จ๊ณ„ ๋ฐฑ์—…ยท๋ณต์›ยทํ˜ธ์ŠคํŠธ ์šฐ์„  ๊ฒ€์ฆ (PORT ๋žœ๋คํ™” + ํด๋ง ๊ฐ•ํ™”) ยง3 dhparam
๋ณด์กฐ verify_healthcheck.sh 6 ์Šคํƒ healthcheck ์ •์  12 PASS + ๋Ÿฐํƒ€์ž„ ์˜ต์…˜ ยง0.5 healthcheck
๋ณด์กฐ verify_nginx_standalone.sh ์‹ค์ œ nginx ์ปจํ…Œ์ด๋„ˆ ๊ธฐ๋™ + ์ „์ฒด ๋งˆ์šดํŠธ + docker cp ๋กœ ์ข…๋ฃŒ ์ปจํ…Œ์ด๋„ˆ์—์„œ๋„ dhparam ์ถ”์ถœ dhparam ํ†ตํ•ฉ
ํ†ตํ•ฉ verify_integration_<stack>.sh ร— 6 6 ์Šคํƒ ํ’€์Šคํƒ ํ†ตํ•ฉ verifier โ€” .env ์ž๋™ ์…‹์—… + docker compose up -d + healthcheck ๋Œ€๊ธฐ + curl Host: localhost HTTP 200 + gzip Vary + ์›Œ์ปค ๊ถŒํ•œ ๊ฐ•ํ•˜ (master root + workers www-data) + dhparam sha256 ์ •ํ•ฉ ๊ฒ€์ฆ + ์ž๋™ cleanup gunicorn / uvicorn / uwsgi / daphne / php73 / php84
๋ณด์กฐ celery_diag.sh / check_cgi.sh / check_cgi2.sh / check_excode.sh / inspect_orphans.sh / sim_exit.sh ๊ฐœ๋ณ„ ์ง„๋‹จ ๋ณด์กฐ ๋‹จ๋ฐœ์„ฑ

์‹คํ–‰ ๋ฐฉ์‹

# ๋‹จ๊ณ„๋ณ„ ์‹คํ–‰ (s0 โ†’ s2 โ†’ s3 โ†’ s5 โ†’ s6 ์ˆœ)
cd /mnt/c/Users/rnd15/Documents/project/github/mig/devspoon-web
bash script/test_run/s0_prereq.sh

# ๋‹จ์ผ ์Šคํƒ smoke (gunicorn)
bash script/test_run/s3_stack_smoke.sh nginx_gunicorn gunicorn gunicorn-app gunicorn

# ๋‹จ์ผ ์Šคํƒ HTTPS ๊ฒ€์ฆ (๋„๋ฉ”์ธ ์—†์ด, certbot ์ œ์™ธ)
# ์ธ์ž: STACK_DIR  STACK  WEBROOT  APPNAME  SERVICE_PORT
bash script/test_run/s5_https.sh nginx_gunicorn gunicorn django_sample gunicorn-app 8000

# dhparam ์˜์†ํ™” ๊ฒ€์ฆ (ยง3 dhparam ์ ˆ์˜ ์ž๋™ํ™”)
bash script/test_run/ssl_diag.sh

# ์ „์ฒด ํšŒ๊ท€
bash script/test_run/s6_regression.sh

# === ํ’€์Šคํƒ ํ†ตํ•ฉ verifier (6 stack, end-to-end) ===
# .env ์ž๋™ ์…‹์—… + compose up + healthcheck ๋Œ€๊ธฐ + HTTP 200 + gzip + ๊ถŒํ•œ ๊ฐ•ํ•˜ + dhparam ๊ฒ€์ฆ
bash script/test_run/verify_integration_gunicorn.sh
bash script/test_run/verify_integration_uvicorn.sh
bash script/test_run/verify_integration_uwsgi.sh
bash script/test_run/verify_integration_daphne.sh
bash script/test_run/verify_integration_php73.sh
bash script/test_run/verify_integration_php84.sh

์ฃผ์˜ โ€” ํ™˜๊ฒฝ ๊ฐ€์ •

  • ๋ชจ๋“  ์Šคํฌ๋ฆฝํŠธ๋Š” ROOT="/mnt/c/Users/rnd15/Documents/project/github/mig/devspoon-web" ๋ฅผ ํ•˜๋“œ์ฝ”๋”ฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๊ฒฝ๋กœ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ฒซ ์ค„์˜ ROOT= ๋ณ€์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š”.
  • s5_https.sh ๋Š” ๋„๋ฉ”์ธ์ด ์—†๋Š” ๋กœ์ปฌ ํ™˜๊ฒฝ ๊ฐ€์ • โ€” certbot ๋ฐœ๊ธ‰์€ ์‹œ๋„ํ•˜์ง€ ์•Š๊ณ  dhparam / nginx https ์ƒ˜ํ”Œ / ๊ฒฝ๋กœ ์ •ํ•ฉ์„ฑ๋งŒ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • s3_stack_smoke.sh ๋Š” ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋„์› ๋‹ค ๋‚ด๋ฆฌ๋ฏ€๋กœ ์šด์˜ ํ˜ธ์ŠคํŠธ์—์„œ๋Š” ์ •๋น„ ์‹œ๊ฐ„๋Œ€์—๋งŒ ์‹คํ–‰.
  • WSL2 ํ˜ธ์ŠคํŠธ์ธ ๊ฒฝ์šฐ script/test_run/*.sh ์‹คํ–‰ ์ง์ „์— chmod 644 compose/web-service/*/redis/conf/redis.conf (๋ฐ ๊ธฐํƒ€ bind-mount ๋Œ€์ƒ) ๊ถŒํ•œ์„ ์žฌํ™•์ธํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. WSL ์˜ ๊ธฐ๋ณธ fmask=177 ์ •์ฑ…์œผ๋กœ ์ธํ•ด 0600 ์œผ๋กœ ์ž˜๋ฆฌ๋ฉด ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ redis ๊ฐ€ conf ๋ฅผ ์ฝ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์˜๊ตฌ ํšŒํ”ผ์ฑ…์€ ยง11 (WSL2 ํ˜ธ์ŠคํŠธ ์šด์˜ ๊ฐ€์ด๋“œ) ์˜ /etc/wsl.conf ์„ค์ •์„ ์ฐธ์กฐ.

11. WSL2 ํ˜ธ์ŠคํŠธ ์šด์˜ ๊ฐ€์ด๋“œ

๋ณธ ํ”„๋กœ์ ํŠธ๋Š” dev ํ™˜๊ฒฝ์—์„œ Windows + WSL2 (Ubuntu) ์œ„์— docker ๋ฅผ ๋„์šฐ๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ด‘๋ฒ”์œ„ํ•˜๊ฒŒ ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค (test_run ์Šคํฌ๋ฆฝํŠธ์˜ ROOT=/mnt/c/... ํ•˜๋“œ์ฝ”๋”ฉ์ด ๊ทธ ํ”์ ). WSL2 ์˜ ๊ธฐ๋ณธ mount ์˜ต์…˜์ด ์ปจํ…Œ์ด๋„ˆ bind-mount ๊ฒฝ๋กœ์˜ ๊ถŒํ•œ์„ ์ž˜๋ผ ์šด์˜์„ ๊นจ๋œจ๋ฆฌ๋Š” ์ผ€์ด์Šค๊ฐ€ ๋ฐ˜๋ณต๋˜๋ฏ€๋กœ ์ด ์ ˆ์—์„œ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์šด์˜(production) ํ™˜๊ฒฝ์€ ๊ฐ€๋Šฅํ•˜๋ฉด Linux native ๋˜๋Š” ํด๋ผ์šฐ๋“œ VM์„ ์‚ฌ์šฉํ•˜์„ธ์š”. WSL2 ๋Š” dev / ๊ฒ€์ฆ ์šฉ๋„์ž…๋‹ˆ๋‹ค.

11.1. /etc/wsl.conf ๊ถŒ์žฅ ์„ค์ •

WSL2 ์˜ ๊ธฐ๋ณธ /mnt/c ๋งˆ์šดํŠธ๋Š” fmask=177 (์ฆ‰ 0600 โ€” ์†Œ์œ ์ž r/w ๋งŒ ํ—ˆ์šฉ) ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ƒํƒœ์—์„œ๋Š” ์ปจํ…Œ์ด๋„ˆ๊ฐ€ redis.conf (0644 ํ•„์š”), www/php_sample/index.php (0644 ํ•„์š”), nginx.conf ๋“ฑ bind-mount ํ•œ ๋ชจ๋“  ํŒŒ์ผ์„ ์ฝ์ง€ ๋ชปํ•ด ์ฆ‰์‹œ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค ("Permission denied" / "Failed to open log file" ๋“ฑ).

WSL2 ์ธ์Šคํ„ด์Šค์˜ /etc/wsl.conf ์— ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š” (์—†์œผ๋ฉด ์ƒ์„ฑ).

[automount]
enabled = true
options = "metadata,umask=22,fmask=11"
  • metadata : Linux ์ธก์—์„œ chmod/chown ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ Windows NTFS ์— ๋ณด๊ด€ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ.
  • umask=22 : ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ธฐ๋ณธ 0755, fmask=11 : ํŒŒ์ผ ๊ธฐ๋ณธ 0644 (์ฆ‰ mount ๋œ ํŒŒ์ผ์ด 0644 ๋กœ ๋…ธ์ถœ).

์„ค์ • ํ›„ PowerShell ์—์„œ:

wsl --shutdown
# ๊ทธ ๋‹ค์Œ WSL ํ„ฐ๋ฏธ๋„ ์žฌ์‹คํ–‰ โ†’ ์ƒˆ ๋งˆ์šดํŠธ ์˜ต์…˜ ์ ์šฉ

๊ฒ€์ฆ:

mount | grep '/mnt/c'
# โ†’ ์˜ต์…˜์— "umask=22,fmask=11" ๊ฐ€ ๋ณด์—ฌ์•ผ ํ•จ
ls -l compose/web-service/nginx_gunicorn/redis/conf/redis.conf
# โ†’ -rw-r--r-- (0644) ๋กœ ๋ณด์—ฌ์•ผ ํ•จ

11.2. dhparam ํ˜ธ์ŠคํŠธ ๋ฐฑ์—…๋ณธ์˜ root ์†Œ์œ  ์ด์Šˆ (ยง3 ์ฐธ์กฐ)

WSL ํ™˜๊ฒฝ์—์„œ๋Š” ์ปจํ…Œ์ด๋„ˆ์˜ dhparam ๋ฐฑ์—… hook ์ด ํ˜ธ์ŠคํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ(./ssl/dhparam/) ์— ํŒŒ์ผ์„ ์“ฐ๋Š”๋ฐ, ์ปจํ…Œ์ด๋„ˆ ๋‚ด root ์†Œ์œ ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ํ˜ธ์ŠคํŠธ ๋น„-root ์‚ฌ์šฉ์ž๋Š” ์ด ํŒŒ์ผ์„ ์ง์ ‘ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋ฉด:

# ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ ์ž‘์—…ํ•˜๊ฑฐ๋‚˜
docker compose exec webserver sh -c 'rm /etc/nginx/dhparam-backup/dhparam.pem'

# WSL ํ˜ธ์ŠคํŠธ์—์„œ sudo ๋กœ ์ฒ˜๋ฆฌ
sudo rm compose/web-service/nginx_gunicorn/ssl/dhparam/dhparam.pem

11.3. WSL /mnt/c ์„ฑ๋Šฅ

/mnt/c ์˜ 9P/Plan9 ๋งˆ์šดํŠธ๋Š” native ext4 ๋Œ€๋น„ IO ๊ฐ€ ~10x ๋А๋ฆฝ๋‹ˆ๋‹ค. dev ์‹œ ์ปจํ…Œ์ด๋„ˆ build ๊ฐ€ ๊ธธ์–ด์ง€๋Š” ์ฃผ๋œ ์›์ธ์ด๋ฉฐ, ์šด์˜ ๊ฐ€์ด๋“œ๋ผ๊ธฐ๋ณด๋‹ค dev ์ƒ์‚ฐ์„ฑ ํŒ์ž…๋‹ˆ๋‹ค โ€” ๊ฐ€๋Šฅํ•˜๋ฉด ํ”„๋กœ์ ํŠธ๋ฅผ ~/projects/devspoon-web (WSL2 native ext4) ๋กœ ์˜ฎ๊ธฐ๊ณ  hardcoded ROOT ๋งŒ ๊ฐฑ์‹ .

11.4. healthcheck timing ๊ณผ WSL

WSL2 ์—์„œ๋Š” ์ปจํ…Œ์ด๋„ˆ ์‹œ์ž‘ ~ healthcheck ์ฒซ ํšŒ ์„ฑ๊ณต๊นŒ์ง€ ์‹œ๊ฐ„์ด native Linux ๋Œ€๋น„ ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. compose ์˜ start_period: 30s ๊ฐ€ ๋งˆ์ง„์„ ๋‘๊ธด ํ•˜์ง€๋งŒ, WSL ํ˜ธ์ŠคํŠธ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ• ์ƒํƒœ๋ผ๋ฉด ๋ถ€์กฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๊ฒฝ์šฐ webserver ๊ฐ€ dependency failed to start: container ... is unhealthy ๋กœ ์‹คํŒจ โ€” ์ฒ˜์Œ ํ•œ ๋ฒˆ timeout ์„ 60s ์ •๋„๋กœ ์ž„์‹œ ์ƒํ–ฅํ•œ ๋’ค ์ •์ƒํ™”๋˜๋ฉด ์›๋ณตํ•ฉ๋‹ˆ๋‹ค.


Community

Partners and Users

About

building web service solution based on nginx supporting php, gunicorn, uwsgi. made by docker, support docker-compose.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors