devspoon-startup-web is an integrated management solution that allows you to easily build the solutions needed for startups (Plane, Jenkins, Gitea [private git server], Harbor [private Docker server]). Docker Compose files can be used to install various development, backup, and management services singly or collectively. This repository is based on the devspoon-web project. devspoon-web is an open source that allows you to easily build a web or API based on php, python, django, nginx, and redis using Docker Compose.
- 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.
- preparing...
-
Plane : Open source project management software (issues · cycles · modules) to help you work on your project efficiently
-
Jenkins : As one of the CI tools, CI (Continuous Integration) refers to continuous integration, which is an automated process for developers, and new code changes are automatically built and tested regularly to notify developers to solve problems that can occur when multiple developers develop simultaneously. Software that helps secure development stability and reliability
-
Gitea : Lightweight self-hosted git service — web UI, issues, pull requests, and git over SSH/HTTP
-
Harbor : The Private Docker Registry Server for businesses that store and distribute Docker Images
-
Web stacks ported from devspoon-web :
compose/web_service/has five stacks —nginx_gunicorn,nginx_uvicorn,nginx_uwsgi,nginx_daphne(Python / Django 6, uv) andnginx_php(PHP 8.4 only). See Web stack & CI. -
User custom installation support : You can selectively install only the desired solution at
compose/project_mng_service/<solution>without having to install all the solutions. Run only one standalone service at a time (see 단독 서비스 동시 기동 규칙). -
All-in-one combinations : To run a web stack together with Plane, Jenkins and Gitea, use one of the five files in
compose/master_service/(see master_service). -
Access web server and project management solutions with one nginx through nginx proxy : In master_service, one nginx serves the web app and reverse-proxies Plane, Jenkins and Gitea by domain.
Example test.com -> company website plane.test.com -> Plane solution jen.test.com -> jenkins solution -
Secret separation (
.env-example) : Every compose folder ships a tracked.env-example, while the actual.envis gitignored. Copy it to.env, then generate the empty secrets withscript/lib/django_secrets.sh(ensure_env_secrets); never commit the live file.${VAR:?}checks in the compose files fail-fast if a required secret is missing. -
etc :
- You can use ssh (port 2222) for git over SSH on Gitea.
- Harbor is installed with its own installer scripts (see Harbor).
-
Development-oriented docker service : This open source is perfect for startups or new service development teams that require frequent modifications and testing.
-
this open-source considers generic servers that are not support AWS, GCM : This open source is intended to be installed and operated on a server that is directly operated, and on general server hosting, and plans to integrate with cloud services such as AWS and GCM in the future
-
Requirements : Docker Engine with the Compose plugin (
docker compose, ≥ 2.17 for the Python app image builds). Legacydocker-compose(v1) commands are not used except by the bundled Harbor installer.
웹 스택 서술은 정본 devspoon-web README 와 같습니다(검수 완료본을 이 저장소 구조에 맞춤). 모든 명령은 저장소 루트 기준입니다.
| 스택 | compose 폴더 | app 서비스 | nginx 설정 폴더 | 프로파일 | 호스트 포트 |
|---|---|---|---|---|---|
| gunicorn | compose/web_service/nginx_gunicorn |
gunicorn-app |
config/web-server/nginx/gunicorn |
celery |
80, 443, 127.0.0.1:5555(flower) |
| uvicorn | compose/web_service/nginx_uvicorn |
uvicorn-app |
config/web-server/nginx/uvicorn |
celery |
80, 443, 127.0.0.1:5555 |
| uwsgi | compose/web_service/nginx_uwsgi |
uwsgi-app |
config/web-server/nginx/uwsgi |
celery |
80, 443, 127.0.0.1:5555 |
| daphne | compose/web_service/nginx_daphne |
daphne-app |
config/web-server/nginx/gunicorn (공유) |
celery |
80, 443, 127.0.0.1:5555 |
| php 8.4 | compose/web_service/nginx_php |
php-app |
config/web-server/nginx/php |
redis |
80, 443 |
- Python 스택은
webserver·app·redis가 항상 뜨고,--profile celery가celery·celery-beat·flower를 더합니다. php 스택은webserver·php-app이 뜨고--profile redis가redis를 더합니다. - 모든 스택이 80/443 을 쓰므로 한 호스트에서 한 스택만 기동합니다.
- 샘플 앱: Python 스택은
www/django_sample, php 스택은www/php_sample.
.env-example 의 비밀값(Python 스택 DJANGO_SECRET_KEY · REDIS_PASSWORD · FLOWER_PWD, php 스택 REDIS_PASSWORD)은 빈 값입니다. compose 가 ${VAR:?} 로 요구하므로 비워 둔 채로는 기동이 거부됩니다. 저장소 루트에서 스택마다 한 번:
D=compose/web_service/nginx_gunicorn
cp "$D/.env-example" "$D/.env"
bash -c ". script/lib/django_secrets.sh && ensure_env_secrets $D/.env"- 값이 비었거나 옛
CHANGE_ME_*인 비밀 키만openssl rand -hex무작위 값으로 채웁니다(DJANGO_SECRET_KEY100 hex,*_KEY_BASE128 hex, 그 외 64 hex). 이미 값이 있는 키는 바꾸지 않습니다. - 같은 폴더의 임시 파일에 쓴 뒤 교체하며, 값을 생성했으면 권한을 600 으로 좁힙니다(더 엄격하면 유지). openssl 이 없거나 실패하면
FAIL로 끝나고.env내용은 바뀌지 않습니다. - 비밀이 아닌 자리표시자는 헬퍼가 채우지 않습니다 — 운영 전에 직접 입력하세요:
FLOWER_ID(CHANGE_ME_FLOWER_USER), master·단독 plane 의PLANE_DOMAIN·PLANE_WEB_URL·PLANE_CORS_ALLOWED_ORIGINS(proxy conf 의server_name과 동일), gitea 의GITEA_DOMAIN·GITEA_ROOT_URL,DJANGO_ALLOWED_HOSTS(도메인 추가). - 호스트에서
manage.py를 직접 실행할 때만www/django_sample의secrets.json이 필요합니다:bash -c '. script/lib/django_secrets.sh && ensure_django_secrets'(없을 때만 생성, 600). 컨테이너는DJANGO_SECRET_KEY환경변수를 씁니다.
업그레이드 노트 — 이전 버전에서 쓰던
.env를 유지하는 경우: 옛.env에는DJANGO_SECRET_KEY줄이 없거나CHANGE_ME_*값이 남아 있을 수 있습니다. 위 헬퍼를 같은.env에 한 번 실행하면 같은 폴더docker-compose*.yml과 그 파일들이include:로 참조하는 조각(compose/common/*.yml)이:?로 요구하는 비밀 키(이름에 SECRET·PASSWORD·PWD 포함 또는_KEY_BASE로 끝남) 중 없는 키를 끝에 추가하고CHANGE_ME_*를 교체하며, 기존 값은 보존하고 권한을 600 으로 맞춥니다.KEY=""처럼 따옴표로 둘러싼 빈 값은 채우지 않으니 먼저KEY=로 고치세요.이전 버전은
compose/web_service/*·compose/master_service·compose/project_mng_service/*의.env를 git 으로 추적했습니다. 이 버전에서 추적이 해제되어git pull이 로컬.env를 지울 수 있으니 pull 전에.env를 다른 곳에 복사해 두세요. 이전에 저장소에 공개됐던secrets.json·.env의 키로 운영 중이라면 새 값으로 교체하세요(세션 무효화).
§1 의 .env 명령(저장소 루트) 뒤, 저장소 루트에서 스택 폴더로 이동해 기동합니다(--build 는 업그레이드나 Dockerfile / uv.lock 변경 뒤 이미지를 다시 빌드합니다). 한 호스트에 한 스택만 띄웁니다.
# gunicorn (저장소 루트에서)
cd compose/web_service/nginx_gunicorn
docker compose up -d --build # webserver + gunicorn-app + redis
docker compose --profile celery up -d # + celery · celery-beat · floweruvicorn · uwsgi · daphne 는 gunicorn 과 같은 명령이며 폴더 이름만 compose/web_service/nginx_uvicorn · nginx_uwsgi · nginx_daphne 로 바꿉니다.
# php (저장소 루트에서)
cd compose/web_service/nginx_php
docker compose up -d --build # webserver + php-app
docker compose --profile redis up -d # + redis- 기동 순서 — app 이 DB 를 초기화한 뒤 celery · beat: 각 스택의 app 서비스만 서버 기동 전에
uv sync후 DB 를 1회 초기화합니다(manage.py가 있으면python manage.py migrate --noinput, 없으면 프로젝트의prestart.sh).celery·celery-beat는depends_on: <app>: condition: service_healthy라 app 이 healthy 가 된 뒤 기동합니다 — 동시 migrate 경쟁이 없습니다. - Flower 는
127.0.0.1:5555에만 바인드됩니다. 원격 접근은 SSH 터널:ssh -L 5555:127.0.0.1:5555 <host>후 로컬 브라우저에서http://127.0.0.1:5555. DJANGO_DEBUG(기본0) ·DJANGO_ALLOWED_HOSTS를.env로 제어하며 app · celery · beat 에 전달됩니다.DJANGO_DEBUG=1은 로컬 개발에서만 쓰세요.- 컨테이너는
docker compose stop/start/restart로 운영합니다. 프로필 서비스까지 대상이면 기동과 같은 프로필을 붙입니다(docker compose --profile celery stop, php 는--profile redis stop) — 프로필 없는stop은 celery · celery-beat · flower(php 는 redis) 컨테이너를 남깁니다.
Python 스택(gunicorn · uvicorn · uwsgi · daphne)의 SQLite 는 호스트 www/<PROJECT_DIR>/db.sqlite3 가 아니라 compose named volume app-data 의 /data/<PROJECT_DIR>.sqlite3 (SQLITE_PATH 환경변수)에 저장됩니다. 컨테이너는 이 볼륨과 로그 디렉터리만 www-data 소유로 맞추고, 호스트 소스 트리(/www)의 소유권은 바꾸지 않습니다. SQLITE_PATH 가 없는 호스트 manage.py 실행은 여전히 www/<PROJECT_DIR>/db.sqlite3 를 씁니다.
기존 DB 이관 (호스트 db.sqlite3 를 계속 쓰려면 — gunicorn 스택 예, celery 프로파일은 이관 후 기동):
cd compose/web_service/nginx_gunicorn
docker compose up -d --build # app-data 볼륨 생성 (빈 DB 로 migrate 됨)
docker compose cp ../../../www/django_sample/db.sqlite3 gunicorn-app:/data/django_sample.sqlite3
docker compose exec gunicorn-app chown www-data:www-data /data/django_sample.sqlite3
docker compose restart gunicorn-app # 기동 명령이 다시 돌며 이관한 DB 에 미적용 migrate 반영 (app 만 — 전체 restart 는 nginx 기동 경합)
⚠️ docker compose down -v는app-data볼륨, 즉 SQLite DB 를 삭제합니다. 컨테이너만 내리려면docker compose stop을 쓰세요 (down은 비권장, 특히-v; 프로필 서비스는 §2 처럼--profile을 붙임). 백업:docker compose cp gunicorn-app:/data/django_sample.sqlite3 ./backup.sqlite3. master_service 의 Plane 데이터 볼륨(plane-pgdata·plane-uploads등)과 Gitea 저장소 볼륨(gitea-data)도-v로 삭제됩니다.
-
이미지 이름은
${IMAGE_NAMESPACE:-devspoon}-nginx:latest형식입니다(-py-app:latest,-uwsgi-app:latest,-php-app:8.4). 기본값이면devspoon-*태그이고, 검증기·verify-ngxblocker.sh는IMAGE_NAMESPACE=devspoon-it, run-ci 빌드 단계(s2_build.sh)는devspoon-test/*태그로 빌드해 운영 태그를 덮어쓰지 않습니다. -
앱 이미지(
py-app·uwsgi-app)의 사전 설치 패키지는www/django_sample/uv.lock에서 도출됩니다. compose 는build.additional_contexts: lock: ../../../www/django_sample로 이를 자동 전달하므로 Docker Compose ≥ 2.17 이 필요합니다 (docker compose version). -
Compose 가 2.17 미만이거나
docker build를 직접 쓸 때는 추가 빌드 컨텍스트를 명시합니다(저장소 루트, BuildKit):docker build --build-context lock=www/django_sample -t devspoon-py-app:latest docker/gunicorn/ docker build --build-context lock=www/django_sample -t devspoon-uwsgi-app:latest docker/uwsgi/
빠뜨리면 빌드가
"/pyproject.toml": not found로 실패합니다. -
uv:
www/django_sample의 의존성은pyproject.toml·uv.lock으로 관리합니다. 컨테이너는 가상환경 없이 시스템 Python 에 설치하며(UV_PROJECT_ENVIRONMENT=/usr/local), 기동 명령이uv sync --inexact --extra <stack> --extra celery를 실행합니다. 같은 스택의 app · celery · celery-beat 는 같은 extras 로 sync 합니다. 의존성 추가는 호스트에서cd www/django_sample && uv add <pkg>후uv.lock을 커밋하고 스택 폴더에서docker compose --profile celery stop && docker compose up -d --build로 재기동하고, celery 사용 시docker compose --profile celery up -d로 celery · celery-beat 도 다시 올립니다(앱 이미지 사전 설치도uv.lock에서 도출; celery 는build:없이 같은 이미지를 참조하므로 프로필 없는up --build만으로는 옛 이미지·옛 의존성으로 계속 동작).
- nginx conf 생성기:
config/web-server/nginx/<gunicorn|uvicorn|uwsgi|php>/의nginx_http_conf.sh·nginx_https_conf.sh가sample_nginx_http(s).conf로conf.d/에 도메인별 conf 를 만듭니다(-h로 옵션 확인). daphne 는 gunicorn 폴더를 씁니다. - nginx 기동 훅: 이미지의
/docker-entrypoint.d/30-wait-upstreams.sh가 conf 의 upstream(앱 컨테이너) 이름이 해석될 때까지 최대NGINX_UPSTREAM_WAIT초(기본 30,0이면 비활성, webserverenvironment로 조정) 기다린 뒤 nginx 를 띄웁니다 — 재부팅·start처럼 app 보다 webserver 가 먼저 뜰 때[emerg] host not found in upstream으로 죽는 것을 막습니다. 전체docker compose restart는 동시 재시작이라 훅으로도 완전히 막히지 않으니 conf 반영은nginx -s reload, 전체 재기동은stop→start를 쓰세요. - php-fpm pool 은
config/app-server/php/pool.d/www.conf를 편집합니다. compose 는www.conf(/usr/local/etc/php-fpm.d/www.conf)와config/app-server/php/php_ini/php.ini만 단일 파일로 읽기 전용 마운트하므로,php_conf.sh가pool.d/에 만드는<DOMAIN>_php.conf는 로드되지 않습니다.
bash script/ci/run-ci.sh 가 11단계를 순서대로 실행합니다: preflight → prereq·로그 디렉터리 → nginx conf 생성기 → compose 검증 → 저장소 고유 검사(script/ci/repo-steps.sh) → 이미지 빌드 → 정적 회귀(s6) → healthcheck → 스택 매트릭스(gunicorn · uvicorn · uwsgi · daphne · php 실기동) → 샘플 프로젝트 → 스크립트 로그.
- 실제 컨테이너를 띄우므로 호스트 80/443/5555 가 비어 있어야 합니다. 필요 도구: docker(Compose ≥ 2.17), uv, jq, curl, openssl, php-cli.
- GitHub Actions(
.github/workflows/test.yml)는 같은 스크립트를 실행합니다. 알림은 선택 — 저장소 secretsSLACK_WEBHOOK_URL,TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID가 없으면 해당 알림을 건너뜁니다. 업로드 로그(log/ci,log/test_run)는script/lib/mask_secrets.sh로 비밀값을 마스킹한 뒤 올립니다.
compose/master_service/ 의 5조합은 한 nginx(webserver) 뒤에 웹 스택과 Plane · Jenkins · Gitea 를 함께 띄웁니다. .env 하나(compose/master_service/.env-example)를 공유하고, Plane · Gitea 서비스 정의는 단독 스택과 같은 compose/common/{plane-services,gitea-service}.yml 을 include: 합니다(Docker Compose ≥ 2.20).
| 파일 | 웹 스택 | 프로파일 |
|---|---|---|
docker-compose-gunicorn.yml |
gunicorn | celery |
docker-compose-uvicorn.yml |
uvicorn | celery |
docker-compose-uwsgi.yml |
uwsgi | celery |
docker-compose-daphne.yml |
daphne | celery |
docker-compose-php.yml |
php 8.4 | redis (celery 없음) |
모든 조합에 Plane(makeplane/plane-*, 앱·DB·큐·오브젝트 저장소·내부 프록시 13 서비스) · jenkins(jenkins/jenkins:lts-jdk21) · Gitea(gitea/gitea, 호스트 2222 = git over SSH) 가 포함됩니다.
-
Plane · Gitea 는 사전 준비가 없습니다 — 관리자 계정은 기동 뒤에 만듭니다(Plane 6 · Gitea 4단계).
-
.env생성 — Plane 비밀 키 5종도 헬퍼가 생성합니다(include:한compose/common/plane-services.yml의${KEY:?}까지 훑습니다).D=compose/master_service cp "$D/.env-example" "$D/.env" bash -c ". script/lib/django_secrets.sh && ensure_env_secrets $D/.env"
이어서
PLANE_DOMAIN·PLANE_WEB_URL·PLANE_CORS_ALLOWED_ORIGINS·GITEA_DOMAIN·GITEA_ROOT_URL·FLOWER_ID자리표시자를 직접 입력합니다. -
proxy 샘플 복사 — webserver 가
config/web-server/nginx/php/proxy/<svc>/를/etc/nginx/proxy.d/<svc>/로 읽기 전용 마운트하고,nginx.conf가include /etc/nginx/proxy.d/*/*.conf;로 읽습니다.conf.d로 복사하지 않습니다.P=config/web-server/nginx/php/proxy cp "$P/plane/plane_proxy.conf.example" "$P/plane/plane_proxy.conf" # server_name 수정 cp "$P/gitea/gitea_proxy.conf.example" "$P/gitea/gitea_proxy.conf" # server_name 수정 cp "$P/jenkins/jenkins_proxy.conf.example" "$P/jenkins/jenkins_proxy.conf" # server_name 수정
복사본(
*_proxy.conf, gitignore)이 없으면 주석뿐인default.conf만 읽혀 해당 proxy 가 비활성입니다. 샘플은 HTTP(80) 서버 블록만 있으므로 TLS 는 HTTPS 절로 443 블록을 추가합니다. -
기동 — 저장소 루트에서 스택 폴더로 이동합니다(
--build는 업그레이드나 Dockerfile /uv.lock변경 뒤 이미지를 다시 빌드합니다).cd compose/master_service docker compose -f docker-compose-gunicorn.yml --profile celery up -d --build docker compose -f docker-compose-php.yml --profile redis up -d --build # php 조합
⚠️ 첫 기동은 Plane 마이그레이션 때문에 수 분 걸립니다:plane-migrator가 성공으로 끝난 뒤plane-api가 뜨고, 그 뒤에plane-proxy가 준비됩니다. Plane · Gitea 데이터는 named volume 이므로 호스트 폴더를 만들 필요가 없습니다(docker compose down -v는 그 볼륨을 지웁니다).
- 단독 서비스는 한 번에 하나만 기동합니다.
nginx_plane·nginx_jenkins·gitea가 모두 호스트 80/443(또는 2222)을 쓰고,plane-*·jenkins·gitea컨테이너 이름이 master_service 와 같습니다. 웹 스택(compose/web_service)·master_service 와도 동시에 띄울 수 없습니다. - 여러 서비스를 동시에 운영하려면 master_service 를 쓰세요.
- 단독 proxy 스택(
nginx_plane·nginx_jenkins·gitea)은 HTTP 전용입니다(80, TLS 없음). 443 은 매핑돼 있지만 catch-alldefault.conf가 TLS 핸드셰이크를 거부(ssl_reject_handshake on)할 뿐 서비스용 TLS 서버 블록이 없어, 로그인 자격증명이 80 으로 평문 전송됩니다. 공개망 운영은 앞단 TLS 종단(별도 리버스 프록시·LB) 뒤에 두거나, TLS 를 구성할 수 있는 master_service 를 쓰세요.
단독 proxy 스택 구조: nginx 가 config/web-server/nginx/php/proxy/<svc>/ 서비스별 폴더를 /etc/nginx/conf.d/ 로 읽기 전용 마운트하고, 그 폴더의 자리표시자 default.conf 자리에 catch-all config/web-server/nginx/php/conf.d/default.conf 를 덮어 마운트합니다. 복사본 <svc>_proxy.conf 가 없으면 모든 Host 에 444 로 응답하며 정상 기동합니다. 자리표시자 default.conf 는 편집·삭제하지 마세요(읽기 전용 마운트 지점).
-
.env생성 — Plane 비밀 키 5종(PLANE_SECRET_KEY·PLANE_LIVE_SERVER_SECRET_KEY·PLANE_DB_PASSWORD·PLANE_MQ_PASSWORD·PLANE_MINIO_PASSWORD)이 비어 있으면 compose 가 기동을 거부합니다. 헬퍼가include:한compose/common/plane-services.yml의 키까지 찾아 채웁니다.D=compose/project_mng_service/nginx_plane cp "$D/.env-example" "$D/.env" bash -c ". script/lib/django_secrets.sh && ensure_env_secrets $D/.env"
-
도메인 3개를 같은 값으로 직접 입력합니다(헬퍼가 채우지 않는 자리표시자):
PLANE_DOMAIN·PLANE_WEB_URL·PLANE_CORS_ALLOWED_ORIGINS. proxy conf 의server_name과도 같아야 로그인·API·실시간 협업(WebSocket)이 모두 동작합니다. HTTPS 로 서비스하면PLANE_WEB_URL·PLANE_CORS_ALLOWED_ORIGINS를https://로 적습니다. -
proxy 샘플 복사(저장소 루트):
P=config/web-server/nginx/php/proxy/plane; cp "$P/plane_proxy.conf.example" "$P/plane_proxy.conf"후server_name수정. -
기동(저장소 루트에서):
cd compose/project_mng_service/nginx_plane && docker compose up -d(HTTP 전용 — 위 단독 서비스 규칙 참조). 첫 기동은plane-migrator가 DB 마이그레이션을 마친 뒤plane-api가 뜨므로 수 분 걸립니다. -
구성: 앱(
plane-web·plane-space·plane-admin·plane-api·plane-live·plane-worker·plane-beat-worker· 1회성plane-migrator) + 데이터(plane-dbPostgreSQL 15 ·plane-redisValkey ·plane-mqRabbitMQ ·plane-minioS3 호환 저장소) + 스택 내부 프록시(plane-proxy, Caddy). 정의는compose/common/plane-services.yml한 곳에 있고 master_service 5조합이 같은 파일을include:합니다 — Docker Compose ≥ 2.20 이 필요합니다. -
첫 계정: 브라우저로 도메인에 접속해 가입하면 그 계정이 인스턴스 첫 사용자가 됩니다. 인스턴스 관리(
/god-mode)는plane-admin이 담당하며 같은 도메인의/god-mode/경로로 열립니다. 외부 가입을 막으려면 God Mode 의 Authentication 설정에서 sign-up 을 끕니다. -
⚠️ 비밀값을 바꾸면 기존 DB 볼륨과 어긋납니다:PLANE_DB_PASSWORD는plane-pgdata볼륨이 처음 만들어질 때의 PostgreSQL 사용자 비밀번호로 굳습니다..env만 새로 만들어(또는ensure_env_secrets를 빈.env에 다시 돌려) 값이 바뀌면plane-migrator가FATAL: password authentication failed for user "plane"으로 실패하고 뒤따르는 앱 컨테이너가 전부 기동하지 못합니다. 값을 바꿀 때는 DB 안의 비밀번호도 함께 바꾸거나(docker compose exec plane-db psql -U plane -c "ALTER USER plane PASSWORD '<새 값>';"), 데이터를 버려도 되면 볼륨을 새로 만드세요(docker compose down -v). -
데이터는 전부 named volume 입니다(
plane-pgdata·plane-uploads·plane-rabbitmq·plane-redisdata·plane-proxy-*·plane-logs-*). 호스트 폴더 소유권 문제가 없는 대신docker compose down -v를 쓰면 전부 삭제됩니다 — 컨테이너만 내릴 때는stop을 쓰세요. 논리 백업 예:D=compose/project_mng_service/nginx_plane # master: D=compose/master_service, 아래 명령에 -f docker-compose-<stack>.yml 추가 (cd "$D" && docker compose exec -T plane-db pg_dump -U plane -d plane) | gzip > ~/plane-db-$(date +%F).sql.gz chmod 600 ~/plane-db-*.sql.gz # 사용자·워크스페이스 데이터가 들어 있다
첨부 파일은
plane-minio볼륨(plane-uploads)에 있습니다:docker run --rm -v plane-uploads:/d -v "$HOME":/b alpine tar czf /b/plane-uploads.tgz -C /d . -
더 자세한 내용은 Plane 공식 문서 참고. 이 저장소의 정의는 업스트림
deployments/cli/community/docker-compose.yml을 기준으로, 호스트 포트를 열지 않고 앞단 nginx 뒤에 두도록 조정한 것입니다.
- proxy 샘플 복사(저장소 루트):
P=config/web-server/nginx/php/proxy/jenkins; cp "$P/jenkins_proxy.conf.example" "$P/jenkins_proxy.conf"후server_name수정. .env생성(저장소 루트):D=compose/project_mng_service/nginx_jenkins; cp "$D/.env-example" "$D/.env"(로그 설정만, 비밀값 없음).- 기동(저장소 루트에서,
--build는 업그레이드나 Dockerfile 변경 뒤 nginx 이미지 재빌드):cd compose/project_mng_service/nginx_jenkins && docker compose up -d --build— 이미지jenkins/jenkins:lts-jdk21, 데이터는 같은 폴더jenkins_home(HTTP 전용 — 위 규칙 참조). jenkins 이미지는 uid 1000 으로 동작하므로, 기동 때마다 1회성jenkins-init서비스가 같은 이미지를 root 로 실행해jenkins_home폴더 소유자를 1000 으로 맞춘 뒤 jenkins 가 뜹니다(클론한 호스트 사용자 uid 가 1000 이 아니어도missing rw permissions on JENKINS_HOME재시작 루프가 생기지 않음 — master_service 도 동일). 그래서 이 폴더는 호스트에서 uid 1000 소유로 보입니다. - There are advanced information in Jenkins Official User Documentation
-
.env생성 후 도메인 입력(비밀값 없음):D=compose/project_mng_service/gitea cp "$D/.env-example" "$D/.env" # GITEA_DOMAIN · GITEA_ROOT_URL 을 실제 도메인으로 수정
GITEA_ROOT_URL은 끝에/를 포함한 전체 URL 입니다(예:http://git.example.com/). HTTPS 로 서비스하면https://로 적습니다 — 웹훅·클론 URL·OAuth 리다이렉트가 이 값을 씁니다. -
proxy 샘플 복사(저장소 루트):
P=config/web-server/nginx/php/proxy/gitea; cp "$P/gitea_proxy.conf.example" "$P/gitea_proxy.conf"후server_name수정. -
기동(저장소 루트에서):
cd compose/project_mng_service/gitea && docker compose up -d— HTTP 는 앞단 nginx 가 프록시하고, git over SSH 는 컨테이너가 호스트2222(GITEA_SSH_PORT)를 직접 게시합니다. 방화벽·보안 그룹에서 2222 를 열어야 외부에서 SSH 클론이 됩니다 — Docker 가 게시한 포트는 호스트 iptables 의 INPUT 체인을 거치지 않으므로, 호스트 방화벽만 열고 클라우드 보안 그룹(예: OCI VCN 보안 목록 · AWS 보안 그룹)을 빠뜨리면10.x사설 IP 로는 되는데 공인 도메인으로는 timeout 이 납니다. -
관리자 계정 생성(최초 1회, 설치 화면은
INSTALL_LOCK으로 건너뜁니다):cd compose/project_mng_service/gitea # master: cd compose/master_service, 아래 명령에 -f docker-compose-<stack>.yml 추가 docker compose exec -u git gitea gitea admin user create \ --username <id> --password '<pw>' --email <mail> --admin
기본값은
GITEA_DISABLE_REGISTRATION=true(관리자만 계정 생성)입니다. 자체 가입을 허용하려면.env에서false로 바꾸세요. -
사용: 웹 UI 에서 저장소를 만들고 SSH 공개키를 등록한 뒤
git clone ssh://git@<도메인>:2222/<계정>/<저장소>.git # SSH git clone http://<도메인>/<계정>/<저장소>.git # HTTP(개인 저장소는 토큰·비밀번호 인증)
-
데이터는 named volume
gitea-data(저장소·SQLite DB·SSH 호스트키) 하나에 모입니다.docker compose down -v는 이 볼륨을 지웁니다. 백업:docker compose exec -u git gitea gitea dump -c /data/gitea/conf/app.ini -f /tmp/gitea-dump.zip && docker compose cp gitea:/tmp/gitea-dump.zip ~/. 외부 DB(PostgreSQL 등)를 쓰려면.env에GITEA__database__*를 추가합니다. -
더 자세한 내용은 Gitea 공식 문서 참고.
- 저장소에 있던 Compose v1 설치 스크립트는 삭제됐습니다. 번들된 Harbor v2.0.0 installer(
install.sh·common.sh)는docker compose플러그인을 쓰지 않고docker-compose라는 이름의 명령을 직접 호출합니다.common.sh의check_dockercompose가docker-compose --version을 실행해 1.18.0 이상으로 파싱하지 못하면[Step 1]에서Need to install docker-compose(1.18.0+) by yourself first and run this script again.를 출력하고 exit 1 로 중단하므로, 레거시 Compose v1(1.18.0+) 바이너리를 운영자가 직접 PATH 에 준비합니다. (버전 판정은 이름이docker-compose인 명령의 출력만 봅니다 — v2 형식 문자열을 내는 같은 이름의 실행 파일도 이 관문을 통과합니다.docker compose플러그인을 부르는 래퍼printf '#!/bin/sh\ncase "$1" in --version|version) exec docker compose version ;; esac\nexec docker compose "$@"\n' | sudo tee /usr/local/bin/docker-compose && sudo chmod +x /usr/local/bin/docker-compose로 http·https 설치와 기동(포털·API·레지스트리 토큰)을 검증했습니다 — 최근 Compose 플러그인은docker compose --version에 버전이 아니라 사용법을 출력하므로 래퍼가version으로 바꿔 전달해야 합니다.) install.sh는 내부에서./prepare를 직접 실행하므로,prepare한 파일만 저장소에 실행 권한(100755)으로 추적됩니다 — 별도chmod없이[Step 3]을 통과합니다. 나머지 스크립트(install.sh·autoinstall.sh·update_harbor_config.sh·common.sh)는100644이며 실행 비트가 필요 없습니다:common.sh는install.sh가source하고, 나머지는 아래 3항처럼bash <스크립트>형태로 실행합니다(autoinstall.sh도 내부에서bash install.sh로 호출합니다)../install.sh처럼 직접 실행하고 싶다면 그 파일에만chmod +x하세요.bash update_harbor_config.sh가 도메인·http 포트·https 여부를 입력받아harbor.yml을 만들고,bash install.sh로 설치합니다.bash autoinstall.sh는 두 단계를 한 번에 실행합니다. 입력하는 경로(ssl·data volume·log)는/data처럼 일반 경로 그대로 입력합니다(이스케이프 불필요, 예전\/data입력도 허용). https 를 쓰려면 설치 전에compose/project_mng_service/harbor-v2.0.0/ssl/letsencrypt/live/<도메인>/{fullchain,privkey}.pem구조로 인증서를 둡니다 —autoinstall.sh가ssl/의 내용을 입력한 ssl path 아래로 복사하고(<ssl path>/letsencrypt/live/<도메인>/…, 기본/etc는 root 권한 필요),harbor.yml인증서 경로에 입력 도메인을 넣습니다.install.sh재실행 시 기존 harbor 컨테이너를down -v로 내린 뒤 다시 올립니다. 설치는 root 로 실행합니다(sudo bash autoinstall.sh/sudo bash install.sh, Harbor 공식 안내와 동일):prepare가common/config/*/env를 root 소유 600 으로 만들기 때문에 일반 사용자로 실행하면[Step 4]에서open …/common/config/jobservice/env: permission denied로 멈춥니다. 이후docker-compose명령(down·ps등)도 같은 폴더에서sudo로 실행합니다.- 공존: Harbor 는 자체 nginx 로 http 포트(기본 80)를 씁니다. 이 저장소의 웹 스택·master_service·단독 proxy 와 같은 호스트라면 별도 호스트를 권장하고, 같은 호스트라면
update_harbor_config.sh에서 다른 http 포트를 지정한 뒤 앞단 nginx(예: master_service proxy conf)에서 그 포트로 프록시하세요. - There are advanced information in Harbor 2.0 Documentation
-
This step requires running http nginx server
-
master_service 는 compose 폴더에
docker-compose.yml이 없으므로 아래 모든docker compose명령에-f docker-compose-<stack>.yml을 붙입니다(예:docker compose -f docker-compose-gunicorn.yml exec webserver bash /script/letsencrypt.sh).-
웹 스택: Run nginx_http_conf.sh located in config/web-server/nginx/. Create a conf file for each domain under config/web-server/nginx//conf.d/. Generated filenames always end with "_http".
-
Start the stack with
docker compose up -d --buildin its compose folder (--build rebuilds the images after an upgrade or a Dockerfile / uv.lock change). This will run the default nginx using http. -
The script/letsencrypt.sh shell script file is linked per volume (
/script). Rundocker compose exec webserver bash /script/letsencrypt.shand enter the domain(s) and email. The ACME webroot is fixed to /www/certbot (every generated conf and proxy sample serves /.well-known/acme-challenge/ from it), so there is no webroot input. -
Now create a conf file for https: run nginx_https_conf.sh located in config/web-server/nginx/, then remove the http conf file from config/web-server/nginx//conf.d/.
-
master_service 의 plane · jenkins · gitea proxy 는 생성기가 없습니다 — 인증서 발급 후
config/web-server/nginx/php/proxy/<svc>/<svc>_proxy.conf에listen 443 ssl서버 블록을 직접 추가합니다(config/web-server/nginx/php/sample_nginx_https.conf의 ssl 지시어 참고). Plane 은.env의PLANE_WEB_URL·PLANE_CORS_ALLOWED_ORIGINS를, Gitea 는GITEA_ROOT_URL을https://로 바꿉니다. -
Apply the new conf in the compose folder:
docker compose exec webserver nginx -t && docker compose exec webserver nginx -s reload(or restart only nginx:docker compose restart webserver). Plane 의PLANE_WEB_URL등이나 Gitea 의GITEA_ROOT_URL을 바꿨다면docker compose up -d plane-api plane-web plane-live·docker compose up -d gitea로 해당 컨테이너만 다시 만듭니다. 전체docker compose restart는 모든 서비스를 동시에 재시작하므로 app 이 내려가는 순간 nginx 가 떠[emerg] host not found in upstream으로 한 번 종료될 수 있습니다(자동 재기동) — 전체 재기동은docker compose stop→docker compose start를 쓰세요. Do not usedocker compose down -v— named volumes (SQLite/data, Plane 데이터, Gitea 저장소) are deleted. -
Certbot 갱신 cron 은 nginx 이미지 안에 내장되어 있습니다 (
docker/nginx/Dockerfile이 빌드 시 crontab 에 등록). 호스트에서 별도crontab설정은 불필요 합니다. 확인:docker compose exec webserver crontab -l.
-
- System integration between Jenkins, Gitea and Plane.
- Support docker-swarm, kubernetes
- docker and orchestration monitoring system
- backup and security system
- Support cloud such as AWS, GCM etc
- Personal Website : Owner's personal website is devspoon.com
- Lim Do-Hyun Owner Developer/project Manager, bluebamus@gmail.com
Personal github.io : bluebamus.github.io