Python Project Packer - 폴더 덩어리 프로젝트를 파이썬 스크립트 1개로 만들어서 통으로 옮기는 방법

1. Python Project Packer? “압축파일 말고, 파이썬 파일 하나만 가져오세요”
회사 내부망, 보안 센터, 폐쇄망 서버…
여러 군데에서 개발을 하다 보면 이런 순간이 옵니다.
“프로젝트 전체를 저기로 옮겨야 하는데…
깃도 막혀 있고, 압축 파일 하나 보내는 것도 귀찮고, 설명도 또 해야 하고…”
이럴 때 폴더 전체를 통째로 담은 파이썬 파일 1개만 딱 건네줄 수 있다면 얼마나 편할까요?
오늘 이야기하는 건 바로 그 아이디어입니다.
- 내 PC에서 프로젝트를 스캔해서
- 하나의 파이썬 스크립트 파일에 모두 담고
- 다른 PC에서는 그 파일 하나만 실행해서
- 원래 폴더 구조 + 파일들을 그대로 복원하는 구조예요.
이걸 저는 편하게 python project packer + self extracting script 조합이라고 부를게요.
2. 이게 왜 이렇게 좋은가?
이 방식을 쓰면 생기는 장점들을 먼저 정리해보면
- “파일 하나만 주세요”라고 말할 수 있음
- 수십, 수백 개의 파일 대신 스크립트 1개만 전달하면 되니까요.
- 내부망 환경에서 특히 유용
- 깃 접근이 안 되는 환경에서,
- 메일·메신저로 파일 주고받기 애매할 때 아주 좋습니다.
- 스냅샷(시점 백업) 용도로도 활용 가능
- “오늘 기준으로 이 상태 그대로 보관해 둬야지” 할 때 packer 한 번 돌리면 끝.
- 동료/후배 onboarding도 간단
- 세팅 방법을 장황하게 설명하기보다
- “이 파일 실행 → 복원 → README 읽고 따라와요”로 끝낼 수 있어요.
- single file deployment 느낌
- 진짜 배포를 대체하진 않더라도,
- 테스트용, 데모용, PoC용으로는 꽤 쓸 만한 single file deployment 느낌을 줄 수 있습니다.
3. 전체 구조 먼저 보기 – packer & unpacker
아이디어 자체는 심플합니다.
- packer 스크립트
- 현재 폴더를 싹 훑으면서
- 포함할 파일들을 하나씩 읽어 “매니페스트(manifest)”라는 큰 딕셔너리(또는 맵)로 만들고
- 그 매니페스트를 JSON 문자열로 직렬화해서
- 미리 준비해 둔 템플릿 스크립트 안에 문자열로 박아 넣습니다.
- 최종 결과물 = 모든 파일이 내장된 self extracting script (언팩커)
- unpacker 스크립트 (self extracting script)
- 자신 안에 박혀 있는 JSON 데이터를 다시 읽어서
- 각 항목별로 폴더를 만들고 파일을 써 줍니다.
- 복원이 끝나면, 원래 프로젝트와 거의 똑같은 구조가 만들어져요.
정리하면,
packer: “프로젝트를 읽어서 → 데이터 덩어리(JSON) + 템플릿에 넣고 → 하나의 스크립트로 저장”
unpacker: “내장된 JSON을 읽어서 → 폴더 만들고 → 파일을 다시 써 줌”
이렇게 두 축으로 돌아가요.
4. packer는 구체적으로 무엇을 하냐?

조금만 더 구체적으로 들어가 보면, 일반적인 packer의 흐름은 대략 이렇습니다. (파일/폴더/프로젝트 이름은 전혀 고정할 필요 없어요.)
- 현재 디렉토리 전체를 순회
os.walk같은 걸로 하위 폴더까지 전부 탐색합니다.
- 무시할 대상은 건너뛴다
- 예를 들어 이런 것들은 보통 자동으로 제외합니다.
.git, 가상환경 폴더,__pycache__- 에디터 설정 폴더, 캐시 파일
- 이미 만들어진 packer/unpacker 관련 파일, 설명서 등
- 필요하면 여기에 로그 폴더, 대용량 데이터 폴더도 추가할 수 있죠.
- 예를 들어 이런 것들은 보통 자동으로 제외합니다.
- 파일별로 텍스트/바이너리 구분
- 텍스트로 열어서 읽어보고(예: UTF-8), 잘 읽히면 텍스트 파일로 저장.
- 예외가 나거나 인코딩이 안 맞으면 바이너리 파일로 간주합니다.
- 텍스트 파일은 그대로, 바이너리는 Base64 인코딩
- 텍스트 파일 → 문자열 그대로
content에 저장 - 바이너리 파일 → 바이트를 읽어서 Base64 문자열로 바꿔서 저장
- 그리고 각 파일마다
{"content": "...", "binary": False/True}
이런 식으로 매니페스트에 넣습니다.
- 텍스트 파일 → 문자열 그대로
- 매니페스트를 JSON으로 직렬화해서 self extracting script 템플릿에 삽입
- 큰 딕셔너리를
json.dumps로 문자열화하고 - 미리 준비해 둔 템플릿 코드 안의
{data_placeholder}같은 위치에 넣어서 - 최종 self extracting script 파일 하나를 생성합니다.
- 큰 딕셔너리를
이제 이 결과물 파일 하나만 있으면, 어디에서든 복원이 가능해집니다.
#packer.py 예시
#프로젝트 루트에서 py manage.py packer 라고 명령을 치시면 됩니다.
#위 명령으로 생성된 파일(unpacker.py)을 원하시는 폴더에 두고 위와 같이 파일명만 달리해서 하시면 됩니다.
import os
import json
import base64
import argparse
import sys
# --- Configuration ---
# 이 리스트에 포함된 파일/폴더는 아카이브에 포함되지 않습니다.
FILES_TO_IGNORE = [
".git",
".venv",
"__pycache__",
".DS_Store",
".vscode",
"*.pyc",
"*.pyo",
"*.pyd",
]
# 아카이브 결과물 파일명
OUTPUT_FILENAME = "unpacker.py"
# --- Unpacker Script Template ---
# 이 템플릿은 최종 결과물인 unpacker.py에 포함될 코드입니다.
UNPACKER_TEMPLATE = """
import os
import json
import base64
# 임베드된 데이터 (Base64로 인코딩됨)
EMBEDDED_DATA = '''
{data_placeholder}
'''
def unpack_project():
'''
임베드된 데이터를 읽어 원본 프로젝트 구조와 파일들을 복원합니다.
'''
print("프로젝트 복원을 시작합니다...")
try:
# 1. Base64 데이터를 디코딩
decoded_data = base64.b64decode(EMBEDDED_DATA.encode('utf-8')).decode('utf-8')
# 2. JSON 파싱
manifest = json.loads(decoded_data)
except (json.JSONDecodeError, TypeError, base64.binascii.Error):
print("오류: 내장된 데이터가 손상되었거나 디코딩에 실패했습니다. 복원을 중단합니다.")
return
total_files = len(manifest)
restored_count = 0
for file_path, info in manifest.items():
try:
# 1. 폴더 구조 생성
dir_name = os.path.dirname(file_path)
if dir_name and not os.path.exists(dir_name):
os.makedirs(dir_name)
print(f" - 폴더 생성: {dir_name}")
# 2. 파일 내용 복원
content = info['content']
is_binary = info.get('binary', False)
if is_binary:
# Base64로 인코딩된 바이너리 파일 디코딩 및 쓰기
file_content = base64.b64decode(content.encode('utf-8'))
with open(file_path, 'wb') as f:
f.write(file_content)
else:
# 텍스트 파일 쓰기
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
restored_count += 1
# print(f" - 파일 복원: {file_path}")
except Exception as e:
print(f"오류: '{file_path}' 파일 복원 중 문제 발생 - {e}")
print(f"\\n총 {total_files}개의 파일 중 {restored_count}개 파일 복원 완료!")
if restored_count == total_files:
print("프로젝트 복원이 성공적으로 완료되었습니다.")
else:
print("일부 파일 복원에 실패했습니다. 로그를 확인해주세요.")
if __name__ == "__main__":
unpack_project()
"""
def is_ignored(path, ignore_list):
"""
주어진 경로가 무시 목록에 해당하는지 확인합니다.
"""
path_parts = path.split(os.sep)
for pattern in ignore_list:
if any(part == pattern for part in path_parts):
return True
# 와일드카드 패턴 매칭 (예: *.pyc)
if pattern.startswith('*') and path.endswith(pattern[1:]):
return True
return False
def is_text_file(filepath):
"""
파일이 텍스트 파일인지 간단히 확인합니다.
"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
f.read(1024) # 첫 1KB만 읽어봄
return True
except (UnicodeDecodeError, IOError):
return False
def pack_project():
"""
현재 프로젝트를 스캔하고 파일들을 데이터로 변환하여
자가 압축 풀림 스크립트(unpacker.py)를 생성합니다.
"""
print("프로젝트 아카이빙을 시작합니다...")
manifest = {}
current_directory = os.getcwd()
for root, _, files in os.walk("."):
# 상대 경로로 변환
relative_root = os.path.relpath(root, current_directory)
if relative_root == ".":
relative_root = "" # 루트 폴더는 경로에 포함하지 않음
# 무시할 폴더인지 확인
if is_ignored(relative_root, FILES_TO_IGNORE):
print(f" - 폴더 무시: {relative_root}")
continue
for filename in files:
file_path = os.path.join(relative_root, filename)
# 무시할 파일인지 확인
if is_ignored(file_path, FILES_TO_IGNORE):
# print(f" - 파일 무시: {file_path}")
continue
try:
if is_text_file(file_path):
# 텍스트 파일 처리
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
manifest[file_path] = {'content': content, 'binary': False}
else:
# 바이너리 파일 처리 (Base64 인코딩)
with open(file_path, 'rb') as f:
binary_content = f.read()
encoded_content = base64.b64encode(binary_content).decode('utf-8')
manifest[file_path] = {'content': encoded_content, 'binary': True}
print(f" - 파일 추가: {file_path}")
except Exception as e:
print(f"오류: '{file_path}' 파일 처리 중 문제 발생 - {e}")
if not manifest:
print("경고: 아카이빙할 파일을 찾지 못했습니다.")
return
# 전체 manifest를 JSON 문자열로 변환 후, 다시 Base64로 인코딩
manifest_json = json.dumps(manifest)
encoded_manifest = base64.b64encode(manifest_json.encode('utf-8')).decode('utf-8')
# 템플릿에 Base64로 인코딩된 데이터 삽입
unpacker_script = UNPACKER_TEMPLATE.replace("{data_placeholder}", encoded_manifest)
# 최종 unpacker.py 파일 생성
try:
with open(OUTPUT_FILENAME, 'w', encoding='utf-8') as f:
f.write(unpacker_script)
print(f"\\n성공: 프로젝트가 '{OUTPUT_FILENAME}' 파일에 성공적으로 아카이빙되었습니다.")
print(f"총 {len(manifest)}개의 파일이 포함되었습니다.")
except IOError as e:
print(f"\\n오류: 최종 아카이브 파일 '{OUTPUT_FILENAME}'을 쓰는 데 실패했습니다 - {e}")
if __name__ == "__main__":
pack_project()5. unpacker는 어떻게 복원할까?

반대로, unpacker(= self extracting script)는 대략 이런 순서로 동작합니다.
- 내장된 JSON 문자열을 읽어 파싱
- 스크립트 상단 어딘가에
EMBEDDED_DATA = '''...'''형식으로 매니페스트 문자열이 들어 있고, - 실행 시 이걸
json.loads로 딕셔너리로 바꿉니다.
- 스크립트 상단 어딘가에
- 파일 개수 파악 → 진행 메시지 출력
- 총 몇 개 파일이 들어 있는지 세고,
- 복원 진행 상황을 콘솔에 출력해줍니다.
- 항목마다 폴더 생성 → 파일 쓰기
- 각 파일 경로에 대해 상위 폴더가 없으면
os.makedirs로 생성하고 binary플래그에 따라- 텍스트면 그냥 문자열을 파일로 쓰고,
- 바이너리면 Base64 문자열을 다시 디코딩해서 바이너리로 씁니다.
- 각 파일 경로에 대해 상위 폴더가 없으면
- 요약 출력
- “총 N개 중 M개 복원 완료” 같은 요약 메시지를 출력하면서 종료합니다.
결국 이 self extracting script 하나만 있으면,
어디에서든 “폴더 구조 + 파일 내용”을 재현하는 자동 설치 프로그램처럼 동작하게 됩니다.
6. 실제로 쓰는 워크플로우 예시
실제 현업에서 쓴다고 가정하고, 최대한 간단하게 적어볼게요.
- 내 PC에서
- 프로젝트 루트에서 packer 스크립트를 실행
- 결과물로 “모든 걸 품은 self extracting script”가 생성
- 파일 전달
- 이 결과물 파일 1개만 USB나 내부망으로 복사/전달
- 추가 폴더, 압축 해제, 깃 클론… 아무것도 필요 없음
- 대상 PC에서
- 결과물 파일을 실행
- 자동으로 폴더와 파일들이 쫙 풀리면서 원래 구조와 거의 동일한 프로젝트가 만들어짐
- 이후에 필요한 경우
- 가상환경 생성
- 의존 패키지 설치
- 데이터베이스 마이그레이션 / 초기 데이터 로드
- 앱 실행
이런 것들을 차례대로 수행하면 됩니다.
이렇게 한 번 맛을 보면, 진짜로 “single file deployment 비슷한 맛”이 나기 때문에 꽤 중독성이 있습니다 😄
7. 주의해야 할 점 (보안·용량)

이 방식은 강력하지만, 동시에 몇 가지 중요한 포인트가 있습니다.
- 소스 코드·데이터가 전부 들어간다
- self extracting script 안에 프로젝트의 모든 파일·데이터가 그대로 포함됩니다.
- 즉, 이 파일 자체가 곧 프로젝트 전체이므로
- 외부 유출, 메신저 전송, 메일 첨부 시 보안에 각별히 주의해야 합니다.
- Base64 때문에 용량이 늘어남
- 바이너리 파일(이미지, DB 파일 등)은 Base64 문자열로 변환되기 때문에
- 원래보다 약 30% 이상 용량이 커지는 경향이 있습니다.
- 무엇을 “포함할지/제외할지” 전략이 중요
- 예를 들어 아래는 보통 제외 후보입니다.
- 캐시/로그 폴더
- 대용량 raw 데이터
- 자동 생성되는 빌드 결과물
- 반대로, 반드시 포함해야 할 것:
- 실제 실행에 필요한 설정 파일, 스크립트, 템플릿, 정적 파일 등
- 예를 들어 아래는 보통 제외 후보입니다.
- 민감한 설정 파일(.env 등)은 상황에 따라
- 내부 전용이라면 포함해도 되지만,
- 외부로 나가거나 여러 사람과 공유되는 파일이라면
- 환경 설정은 따로 배포하도록 설계하는 게 안전합니다.
8. 초보자를 위한 용어 정리
마지막으로, 이 글에 나온 용어들을 짧게 정리해둘게요.
- 아카이빙(Archiving)
여러 파일과 폴더를 한 번에 묶어서 보관하거나 옮기기 좋은 형태로 만드는 것.
여기서는 “프로젝트 전체를 스캔해서 한 개의 스크립트로 만드는 과정”을 말합니다. - python project packer
파이썬으로 작성된,
“프로젝트를 읽어서 → 데이터로 만들고 → self extracting script로 뽑아주는” 자동 포장 도구를 통칭하는 표현입니다. - self extracting script
안에 데이터와 복원 로직을 동시에 가지고 있는 파이썬 스크립트.
별도의 압축 해제 프로그램 없이 자기 자신을 실행해서 폴더와 파일을 복원합니다. - single file deployment
여러 리소스를 하나의 파일로 묶어 파일 하나만 배포하면 되는 방식.
여기서는 결과 스크립트를 복사하고 실행하는 것만으로 거의 배포에 가까운 효과를 내는 걸 뜻합니다. - 매니페스트(Manifest)
“어떤 파일이 어디에 있었고, 내용은 무엇이었는지”를 정리한 목록(사전형 데이터).
unpacker가 이 매니페스트를 보고 폴더와 파일을 재구성합니다. - 텍스트 파일 / 바이너리 파일
- 텍스트 파일: 사람 눈으로 읽을 수 있는 문자 기반 파일 (코드, 설정, 문서 등).
- 바이너리 파일: 이미지, DB, 실행 파일처럼 임의의 바이트 데이터로 구성된 파일.
- Base64 인코딩
바이너리 데이터를 텍스트(문자열)로 바꾸는 방법.
이 덕분에 바이너리도 JSON 안에 안전하게 넣을 수 있지만, 용량이 약간 늘어납니다. - 무시 목록(ignore list)
아카이빙에서 제외할 파일/폴더 패턴 목록..git, 가상환경, 캐시 폴더 등은 여기에 넣어두면 좋습니다. - 스냅샷(Snapshot)
특정 시점의 프로젝트 상태를 그대로 저장해 둔 것.
나중에 문제가 생겼을 때, 이 시점으로 되돌아가는 기준점 역할을 합니다.
혹시 또 다른 팁이 궁금하시면 CMD 명령어를 이용한 폴더 파일 목록 추출하는 방법(feat. 파워셸) 포스트도 참고해보세요!





