sideproject · 2026-09-26 · 조회수 119
AI가 매번 .env를 채워 달라고 해서 만든 도구, byulsol.labs
2026-09-30 업데이트 — byulsol secrets를 byulsol.labs로 확장하며 프로젝트 관리, Ubuntu·Docker 대시보드, 운영 정보 MCP와 계정 비밀번호 저장 내용을 추가했다.
AI와 함께 개발하다 보면 익숙한 요청을 받는다.
.env에 API 키를 설정해 주세요.
프로젝트 하나라면 파일을 열어서 넣으면 된다. 그런데 다른 프로젝트를 만들면 비슷한 일을 다시 한다. 어떤 키가 필요한지 확인하고, 개발용과 운영용을 구분하고, 설정 파일을 어느 폴더에 넣어야 하는지도 챙긴다. 코드는 AI가 작성해도 설정을 준비하는 일은 계속 내 몫이었다.
그래서 프로젝트별 설정을 한곳에 저장하고, AI가 필요한 항목을 준비한 뒤 빌드할 때 가져다 쓰게 만들었다. 처음 이름은 byulsol secrets였다. 집의 Ubuntu 서버에 Docker로 올려 두고 웹과 MCP, CLI를 연결했다. 지금은 프로젝트와 서버 관리까지 붙인 byulsol.labs로 확장해 사용하고 있다.

초기 byulsol secrets 웹 화면에 공개용 예시 데이터를 넣었다. 왼쪽에서 프로젝트와 환경을 고르면, AI가 준비한 입력 대기 항목을 확인할 수 있다.
키를 보관하는 것에서 한 걸음 더
처음 생각은 단순했다. DB 접속 정보나 API 키를 중앙에 두고, 프로젝트에서 필요할 때 가져오면 되지 않을까?
막상 사용 흐름을 따져 보니 저장 화면만으로는 부족했다. AI가 필요한 변수 이름을 알아야 하고, 내가 아직 입력하지 않은 항목도 구분해야 했다. 값이 준비되면 해당 프로젝트의 빌드 명령까지 연결되어야 했다.
그래서 작업을 세 부분으로 나눴다.
- AI: 소스를 보고 필요한 설정의 이름, 용도, 환경, 전달 방식을 정한다.
- 사용자: 웹에서 실제 값을 입력하거나 파일을 업로드한다.
- CLI: 실행 권한을 확인하고 선택한 설정을 빌드·실행 명령에 전달한다.

AI는 이름과 준비 상태를 다루고, 실제 값은 웹에서 저장한 뒤 CLI가 가져온다. 동작을 설명하기 위한 개념도다.
예를 들어 새 프로젝트에 외부 API가 필요하면 AI가 PAYMENT_API_KEY라는 항목과 용도를 먼저 등록한다. 나는 안내받은 웹 화면에서 키를 저장한다. 이후 AI는 입력 완료 여부를 확인하고 그 키가 필요한 명령을 실행한다. 키를 대화창에 붙여 넣는 단계는 없다.
MCP와 프로젝트 지침을 함께 둔 이유
AI에게 매번 “우리 중앙 설정 서버를 사용해 줘”라고 설명하면 또 다른 반복 작업이 된다. 공통 개발 지침에 중앙 등록 절차를 남기고, AI가 실제로 호출할 수 있는 MCP 도구도 붙였다.
지침에는 언제 중앙 설정을 사용해야 하는지 적었다. MCP는 프로젝트 목록을 확인하고, 새 프로젝트와 입력 대기 항목을 만들고, 입력 상태와 실행 준비 상태를 조회한다. 각 프로젝트에는 연결된 프로젝트 ID와 실행 방법을 남겨 다음 작업에서도 재사용한다.
둘은 맡은 일이 다르다. 지침이 작업 방향을 알려 준다면, MCP는 지금 무엇이 등록되어 있고 무엇이 빠졌는지 확인할 수 있게 한다.
이 프로젝트의 MCP는 비밀 값을 반환하지 않는다. AI가 받는 것은 변수 이름, 형식, 입력 여부 같은 정보다. 실제 값은 인증된 CLI가 가져와 자식 프로세스에 전달한다. AI가 명령을 구성하는 데 키의 내용까지 알 필요는 없기 때문이다.
다만 이 구조가 실행되는 프로그램의 행동까지 막아 주지는 않는다. 빌드 스크립트가 환경변수를 출력하면 로그에 남을 수 있다. 그래서 필요한 항목만 선택해서 전달하고, 값을 출력하는 명령은 피하도록 했다.
빌드할 때 넣을 값과 서버가 실행 중에 쓸 값
처음에는 중앙에서 값을 가져와 설정 파일을 채우고 빌드하면 끝이라고 생각했다. 실제로 앱에 포함되는 공개 설정은 그렇게 사용할 수 있다. 하지만 서버의 DB 비밀번호처럼 실행 중에 필요한 값도 있다.
byulsol secrets에서는 명령을 실행하는 시점에 설정을 전달하도록 만들었다. 빌드에 필요하면 빌드 명령을, 서버 실행에 필요하면 서버 실행 명령을 CLI로 감싼다. 프로젝트가 .env나 application.properties를 요구한다면 전달받은 환경변수로 파일을 만들거나, 파일 자체를 받아 사용하는 방식으로 연결한다.
등록이 끝났다고 기존 빌드 명령이 저절로 바뀌는 것은 아니다. 프로젝트가 설정을 읽는 위치와 실행 명령을 한 번 연결해야 한다. 이 연결 작업도 AI가 소스를 확인하고 수행하도록 지침에 넣었다.
브라우저에 전달해도 되는 공개 설정과 서버에서만 사용할 비밀 값도 구분한다. 개발 환경에 입력했다고 운영 환경까지 자동으로 열리지는 않는다. 운영 명령에는 별도의 환경 접근 허용과 명시적인 실행 옵션이 필요하다.
환경변수만으로 끝나지 않았다
우리콕 개발을 이어 가다 보니 다음 질문이 생겼다.
google-services.json이나key.properties도 여기서 관리할 수 없을까?
설정을 옮기는 번거로움은 한 줄짜리 키에만 있는 게 아니었다. Android 프로젝트에는 정해진 위치에 있어야 하는 JSON 파일이 있고, 서명에는 비밀번호와 키스토어 파일이 필요하다. 변수만 중앙에 두면 이런 파일은 여전히 따로 챙겨야 했다.
그래서 웹에서 텍스트 파일과 바이너리 파일을 업로드할 수 있게 확장했다. JSON은 JSON 형식으로, properties 파일은 여러 줄 텍스트로, .jks와 .keystore는 바이너리로 관리한다. 바이너리는 비공개 파일 전달만 허용한다.

입력 대기의 ‘값 등록’을 누르면 파일을 선택할 수 있다. 캡처에는 테스트용 파일을 사용했으며 실제 서명 키는 포함하지 않았다.
이 파일들이 모두 같은 성격의 비밀은 아니다. Firebase의 google-services.json에는 앱과 프로젝트를 연결하는 공개 식별 정보가 들어가며, 관리자 서비스 계정의 개인 키와 구분해야 한다. 이 차이는 Firebase의 설정 파일 설명에서도 확인할 수 있다. 중앙에 모으는 이유에는 비밀 보호뿐 아니라 프로젝트별 설정을 빠뜨리지 않고 준비하려는 목적도 있다.
key.properties는 서명 비밀번호와 키스토어 위치를 참조하는 별도 설정이다. 파일을 올리는 것만으로 서명 준비가 끝나지는 않는다. 실제 키스토어와 빌드 설정이 함께 맞아야 한다. 이 관계는 Flutter의 Android 서명 안내를 기준으로 구분했다.
파일 내용은 중앙에, 사용 경로는 프로젝트에
파일을 저장한 다음에는 어디에 놓을지가 남는다. 같은 이름의 설정이라도 프로젝트마다 폴더 구조가 다를 수 있다. 내 PC의 절대 경로를 중앙에 넣으면 다른 작업 환경에서 그대로 쓰기도 어렵다.
그래서 중앙에는 파일 내용을 저장하고, 프로젝트에는 이름과 상대 경로만 적은 매핑 파일을 둔다.
{
"version": 1,
"files": [
{
"name": "GOOGLE_SERVICES_JSON",
"path": "android/app/google-services.json"
}
]
}
이 파일은 .byulsol/files.json에 두는 예시다. 비밀 값은 없고, 어떤 설정을 어디에 놓을지만 담는다. 실제 경로는 AI가 프로젝트의 빌드 설정을 확인해서 정한다.

경로 매핑과 실제 파일 내용은 따로 관리한다. 기존 파일이 있으면 덮어쓰지 않고 실행을 멈춘다.
실행 명령은 아래 형태다. PROGRAM ARGS는 해당 프로젝트에서 확인한 실제 프로그램과 인수로 바꾼다.
byulsol run --env dev --only GOOGLE_SERVICES_JSON --files .byulsol/files.json -- PROGRAM ARGS
CLI는 선택한 파일을 지정된 위치에 배치하고 명령을 실행한다. 정상 종료뿐 아니라 명령이 실패했을 때도 이번 실행에서 만든 파일을 정리한다. 기존 파일을 덮어쓰거나 프로젝트 밖에 파일을 생성하는 경로는 허용하지 않는다. 대상 파일은 Git 제외 목록에도 넣는다.
서명 설정에 들어가는 키스토어 경로도 같은 기준으로 맞춰야 한다. 한 PC에서만 유효한 절대 경로를 저장하는 대신, 빌드가 읽는 상대 경로나 주입된 경로를 사용한다.
키·파일 전달에서 확인한 범위
현재 중앙 서버는 Ubuntu의 Docker에서 동작하고, 설정을 가져와 명령을 실행하는 CLI는 Windows를 지원한다. 웹에서 JSON·properties·바이너리 파일을 등록하고, CLI가 원래 내용으로 전달한 뒤 정리하는 흐름을 테스트했다. 실제 Java 프로세스가 파일과 properties를 읽는 것도 확인했다.
파일은 하나당 최대 64KiB로 제한했다. 큰 자산을 보관하는 저장소보다는 설정 파일과 키 파일을 관리하는 도구에 가깝다. 프로세스를 강제로 종료하거나 전원이 꺼지면 임시 파일이 남을 수 있으며, 다음 실행에서는 기존 파일 충돌로 멈춘다. 빌드 프로그램이 다른 곳에 복사한 파일까지 자동으로 지워 주지는 않는다.
이번 검증에는 테스트용 파일을 사용했다. 우리콕의 실제 서명 키로 배포하거나 외부 서비스 인증까지 확인한 단계는 아니다. 중앙 등록과 파일 전달이 동작하는 것, 실제 서비스에 연결되고 서명이 유효한 것은 각각 확인해야 한다.
이렇게 프로젝트에 필요한 설정의 목록과 사용 방법을 AI가 준비하고, 나는 웹에서 값과 파일을 채우는 흐름이 갖춰졌다. 이 도구를 여러 프로젝트에 쓰면서 다음으로 정리하고 싶은 정보도 생겼다.
프로젝트가 늘어나자 URL과 실행 방법도 흩어졌다
키를 중앙에 모으고 나니 다른 정보가 눈에 들어왔다. 이 프로젝트는 어느 주소로 접속하는지, 로컬에서는 몇 번 포트를 쓰는지, 서버에서는 어느 폴더에서 실행하는지 매번 다시 찾아야 했다. 관리자 계정까지 따로 적어 두면 키 관리 화면만 열어서는 작업을 이어 가기 어려웠다.
그래서 기존 도구에 프로젝트 관리와 서버 대시보드를 붙이고 이름을 byulsol.labs로 바꿨다. 로그인하면 대시보드가 열리고, 프로젝트 관리·서버·Secrets로 이동한다. 기존에 저장한 키와 파일, CLI와 MCP 연결은 유지했다.
프로젝트 상세에는 접속 URL, 호스트와 컨테이너의 포트, 연결된 서버, 실행 폴더와 셸, 실행 명령, 계정 정보를 모았다. 저장소 주소와 담당자, 개발 중·운영 중 같은 상태도 함께 남긴다. 접속 정보와 명령, 계정은 개발·운영 환경을 나눠서 관리한다.

2026-09-30에 추가한 화면부터는 운영 중인 앱과 같은 웹 자산에 공개용 예시 데이터를 넣어 직접 캡처했다. 프로젝트명, 주소, 계정과 서버 수치는 실제 운영 데이터가 아니다.
실행 방법에는 명령만 적지 않고 어느 폴더에서 어떤 셸로 실행하는지도 기록한다. 화면의 ‘명령 복사’로 가져다 쓸 수 있고, 키가 필요한 실행은 기존 CLI에 연결한다. 명령을 저장했다고 웹에서 바로 실행되는 구조는 아니다.
프로젝트 정보도 AI가 확인해서 등록하게 했다
이 정보를 매번 손으로 채우면 관리할 문서가 하나 더 생긴다. 개발을 맡긴 AI가 소스와 실행 설정을 이미 보고 있으니, 그때 확인한 내용을 중앙에도 남기도록 했다.
MCP에 운영 정보 조회와 부분 갱신 도구를 추가했다. AI는 byulsol_get_operations로 기존 내용을 읽고, 현재 소스·설정·실행 결과에서 확인한 항목만 byulsol_sync_operations로 저장한다. URL과 포트뿐 아니라 실행 폴더, 셸, 명령, 계정 ID도 대상이다.
예를 들어 개발 서버를 실행해 주소와 포트를 확인했다면 접속 정보를 등록하고, 실제로 사용한 실행 명령을 같은 프로젝트에 남긴다. 다음 작업에서는 이미 등록된 프로젝트 연결과 운영 정보를 다시 읽는다. 모르는 주소나 계정은 추측해서 채우지 않는다.
기존 내용을 보존하는 것도 신경 썼다. 보내지 않은 항목은 유지하고, AI가 정보를 읽은 뒤 사용자가 먼저 수정했다면 버전 충돌로 저장을 멈춘다. 다시 조회해서 필요한 변경만 반영하도록 했다.
모든 프로젝트를 백그라운드에서 찾아다니며 자동 수집하는 기능은 아니다. 연결된 MCP와 프로젝트 지침을 사용하는 AI가 승인된 개발·빌드·배포 작업 중 확인한 정보를 등록하는 방식이다. 메타데이터를 저장하는 것과 실제 명령을 실행하거나 배포하는 권한도 구분한다.
집 Ubuntu와 Docker를 대시보드에 붙였다
집 Ubuntu에는 이 도구 외에도 여러 서비스가 Docker로 돌아간다. 프로젝트의 접속 주소를 확인하면서 서버 상태도 같이 보고 싶었다.
우선 집 Ubuntu부터 수집기를 연결했다. 대시보드에는 CPU·메모리·디스크 사용률과 마지막 수집 시각을 표시한다. 자원 사용률은 80% 미만이면 녹색 ‘정상’, 80% 이상이면 주황색 ‘주의’, 90% 이상이면 빨간색 ‘위험’으로 구분했다. 색만 보지 않아도 상태를 읽을 수 있도록 문구를 함께 둔다.

정상·주의·위험을 비교할 수 있도록 만든 예시 수치다. 실제 집 서버에 디스크 부족이나 Docker 장애가 발생했다는 뜻은 아니다.
마지막으로 수집한 값이 오래됐다면 그대로 초록색을 보여 줘서는 안 됐다. 수집이 지연된 서버는 이전 측정값임을 표시하고, 그 수치를 현재의 자원 경보에 포함하지 않도록 했다.
Docker는 Compose 프로젝트별로 묶었다
컨테이너를 전부 한 줄씩 나열하니 같은 서비스의 웹, 워커, DB가 길게 이어졌다. 그래서 서버 안에서 Compose 프로젝트별로 묶고, 필요한 묶음만 펼쳐 보게 했다. 같은 이미지를 사용하더라도 서로 다른 프로젝트라면 합치지 않는다. Compose에 속하지 않는 컨테이너는 따로 모은다.

예시 worker는 실행 중이지만 헬스 검사는 비정상이다. 실행 상태와 헬스 검사를 별도 열과 색상으로 표시한다.
실행 중이라는 표시와 서비스가 정상이라는 표시는 구분해야 했다. 헬스 검사 실패나 비정상 종료는 위험으로, 재시작 중인 상태 등은 주의로 표시한다. 정상 종료한 일회성 작업은 중립색으로 남기고, 확인이 필요한 묶음을 먼저 보여 준다.
현재 Docker 기능은 목록·상태·포트를 확인하는 범위다. 웹에서 컨테이너를 시작하거나 중지하고 로그를 여는 기능은 넣지 않았다. 웹 앱과 수집기에도 Docker 소켓을 직접 연결하지 않고, 필요한 목록 정보를 따로 수집해 전달한다.
계정 비밀번호도 같은 화면에서 저장한다
계정 ID와 로그인 주소를 기록하니 비밀번호도 함께 관리할 필요가 생겼다. 프로젝트의 계정 추가 화면에서 직접 입력하거나, 이미 저장된 Secrets 항목을 연결할 수 있게 했다.

로그인 주소와 계정 ID는 예시이며, 비밀번호 입력란은 비운 상태로 촬영했다.
입력한 비밀번호는 기존 Secrets 암호화 저장소에 넣고, 계정 정보에는 해당 항목의 ID만 연결한다. 비밀번호를 다시 저장할 때는 새 항목을 만들어 연결한다. 기존 항목을 다른 계정이나 실행 설정에서도 쓰고 있을 수 있기 때문이다.
비밀 값 열람에는 기존 관리자 재인증을 적용한다. AI가 운영 정보를 조회할 때도 비밀번호 원문은 받지 않는다. AI는 같은 환경에 저장된 비밀의 이름이나 ID로 연결하고, 실제 값은 사용자가 웹에서 입력하는 흐름을 유지했다.
프로젝트 선택과 메뉴 이동도 다시 정리했다
기능을 붙이고 나서 직접 써 보니 메뉴의 흐름이 어색했다. ‘프로젝트 관리’ 목록에서 프로젝트를 열면 운영 정보가 나오는데, 왼쪽에서 같은 프로젝트 이름을 누르면 Secrets 화면으로 이동했다. 운영 환경을 보고 있다가 프로젝트 목록을 접으려다 선택한 환경이 바뀌는 경우도 있었다.
지금은 목록과 왼쪽 프로젝트 이름이 같은 상세 화면을 연다. 같은 프로젝트를 다시 눌렀을 때는 보고 있던 환경과 탭을 유지하고, 펼치고 접는 동작은 옆의 화살표가 맡는다. 환경을 바꿔도 현재 기능을 유지하도록 맞췄다.

프로젝트 상세를 보는 동안 전역 메뉴는 ‘프로젝트 관리’에 남는다. 그 안에서 프로젝트 정보와 Secrets 등 필요한 기능을 고른다.
글꼴은 Pretendard로 바꾸고, URL과 명령은 고정폭 글꼴로 구분했다. 긴 비밀 이름 때문에 팝업이 옆으로 밀리던 문제도 수정했다. 팝업의 제목과 닫기 버튼, 하단 동작은 화면 안에 두고 내용 영역이 스크롤되도록 정리했다.
프로젝트 진입 경로, 환경·탭 유지, 펼침 동작은 회귀 테스트로 확인했고 데스크톱과 모바일에서도 점검했다. 이 변경은 집 Ubuntu의 운영 앱에 반영했다.
처음에는 .env를 반복해서 채우는 일이 번거로워 시작했다. 지금은 프로젝트를 열면 필요한 키뿐 아니라 접속 주소와 실행 방법을 찾고, 같은 도구에서 집 서버와 컨테이너 상태를 확인한다. AI가 작업 중 확인한 정보를 여기에 남기게 한 것도 다음에 그 프로젝트를 다시 열었을 때 같은 질문부터 반복하지 않기 위해서다.
댓글