Mac 백그라운드 서버 실행
맥미니에서 Flask 기반 간단한 웹서버 운영중이야. 이거 좀 백그라운드에서 자동으로 띄우는 법 알려줘. 지금은 VS Code 내부의 터미널에서 돌리고 있는데 실수로 끌 것 같아.
맥미니에서 Flask 웹서버를 백그라운드에서 자동 실행되도록 하려면 다음과 같은 방법들이 있습니다. 귀찮더라도 정확하게 처리해야 실수 없이 운영할 수 있습니다.
1. macOSlaunchd 사용 (권장)
macOS에서 서비스나 데몬을 관리하는 기본 방식입니다.
1.1. plist 파일 생성
예시: ~/Library/LaunchAgents/com.user.flaskserver.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.flaskserver</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/python3</string>
<string>/Users/yourusername/path/to/your/flask_app.py</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/yourusername/path/to/your/</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/flaskserver.out</string>
<key>StandardErrorPath</key>
<string>/tmp/flaskserver.err</string>
</dict>
</plist>
⚠️ yourusername, 경로 등은 반드시 본인의 환경에 맞게 수정해야 합니다.
1.2. 등록 및 실행
launchctl load ~/Library/LaunchAgents/com.user.flaskserver.plist
launchctl start com.user.flaskserver
2. nohup + & + cron 사용 (비권장, 임시)
터미널에서 그냥 끄지 않고 돌리려는 단순한 방법이지만, macOS에서는 재부팅 이후 유지가 안 됨.
nohup python3 /Users/yourusername/path/to/your/flask_app.py > flask.log 2>&1 &
백그라운드로 실행되고, flask.log에 출력이 저장됩니다.
하지만 이건 터미널을 닫으면 살아있을 수는 있어도, 맥을 재부팅하면 사라집니다. launchd가 더 낫습니다.
3. Python에서 직접Flask 실행하는 방식 점검
혹시 VS Code에서 다음처럼 실행 중이라면:
app.run()
를 host='0.0.0.0', port=5000으로 명시해주는 것도 추천드립니다:
app.run(host='0.0.0.0', port=5000)
결론
-
실수로 터미널을 꺼서 Flask가 내려가는 걸 막고 싶다면,
launchd가 가장 macOS 친화적이고 안정적인 방식입니다. -
nohup은 일시적으로만 적합하고 운영에 쓰기엔 취약합니다. -
Python 앱을 서비스처럼 운영하고 싶다면 나중에는
gunicorn같은 WSGI 서버 +nginx조합도 고려해야 합니다.
필요하면 plist 파일 작성이나 자동화 스크립트도 도와드릴 수 있습니다. 어느 쪽이 더 편하신가요?
plist 방식으로 하면 잘 도는지 확인은 어떻게 해?
launchd로 Flask 서버를 plist로 띄웠다면, 동작 상태 확인과 디버깅은 명시적이고 단계적으로 해봐야 합니다.
절차적으로 설명드리겠습니다.
✅ 1. 실행 상태 확인
launchctl list | grep com.user.flaskserver
-
아무것도 안 나오면: 아직 로드 안 된 상태.
-
나오는데 PID가 없고
Exit status가 1이나 다른 값이면: 실행 실패. -
정상일 경우 PID가 있고
0이면 실행 중.
예시:
diff
- 0 com.user.flaskserver
-가 PID 자리면 백그라운드에서 실행 안 됨. 뭔가 실패한 거임.
✅ 2. 로그 확인 (stdout / stderr)
plist에서 지정한 로그 경로를 확인:
cat /tmp/flaskserver.out
cat /tmp/flaskserver.err
-
out은 정상 실행 시 출력 -
err는 에러 발생 시 출력
여기에서 대부분의 원인 파악 가능 합니다.
예:
vbnet
ModuleNotFoundError: No module named 'flask'
→ LaunchAgent에서는 가상환경을 못 인식함. (해결은 아래 참고)
✅ 3. 서비스 재시작 / 재로드
launchctl unload ~/Library/LaunchAgents/com.user.flaskserver.plist
launchctl load ~/Library/LaunchAgents/com.user.flaskserver.plist
재로드 없이 수정하면 반영 안 됨. 매번 이렇게 해야 함.
✅ 4. plist에 절대경로 썼는지 확인
예를 들어 python이라고만 쓰면 launchd 환경에서는 PATH 설정이 안 되어 있어서 못 찾습니다. 절대경로로 넣어야
함.
which python3
# 예: /opt/homebrew/bin/python3
그걸 plist에 넣어야 합니다.
✅ 5. Flask 앱 직접 확인
서버가 잘 떴는지 Flask 포트로 접속 시도:
curl http://localhost:5000
브라우저로도 접근 가능해야 합니다.
🚨 가상환경 쓴다면?
venv 쓰는 경우엔 plist의 ProgramArguments를 다음처럼 바꿔야 합니다:
<array>
<string>/Users/yourusername/venv/bin/python</string>
<string>/Users/yourusername/path/to/app.py</string>
</array>
그렇지 않으면 flask, requests 등 모듈 인식 못 해서 실패합니다.
정리
| 체크 항목 | 설명 |
|---|---|
launchctl list |
프로세스 로드/실행 상태 확인 |
| 로그 파일 | stdout, stderr 내용 꼭 확인 |
| 절대경로 | python이나 앱 경로 모두 절대경로로 지정 |
curl 테스트 |
Flask 서버가 실제로 실행 중인지 확인 |
plist 수정 후 재로드 |
수정하면 꼭 unload → load 해야 적용됨 |
필요하시면 plist 작성 샘플에 사용자 계정이나 가상환경 포함해서 구성해드릴 수 있습니다. 지금 사용 중인 Flask 앱 경로랑
Python 실행 위치 알려주시면 됩니다.
수정하거나 없애려면 어떻게 해?
launchd로 등록한 Flask 서비스(plist)를 수정하거나 완전히 없애는 법 을 단계적으로 설명드리겠습니다. 실수로
잘못 남겨두면 자동 실행돼서 나중에 트러블 생깁니다. 꼼꼼히 처리하셔야 합니다.
🔧 1. 수정하려면
1.1. 먼저 언로드(중지)
launchctl unload ~/Library/LaunchAgents/com.user.flaskserver.plist
→ 수정 없이 바로 파일 고치면 반영 안 됩니다. 항상 unload 먼저.
1.2. plist 파일 수정
예시:
nano ~/Library/LaunchAgents/com.user.flaskserver.plist
수정 후 저장하고 나와서:
1.3. 다시 로드
launchctl load ~/Library/LaunchAgents/com.user.flaskserver.plist
정상 작동 여부는:
launchctl list | grep com.user.flaskserver
❌ 2. 완전히 없애려면 (삭제)
2.1. 먼저 언로드
launchctl unload ~/Library/LaunchAgents/com.user.flaskserver.plist
2.2. plist 파일 삭제
rm ~/Library/LaunchAgents/com.user.flaskserver.plist
2.3. 백그라운드에서 아직 남아있는지 확인
launchctl list | grep com.user.flaskserver
안 나오면 제대로 삭제된 겁니다.
🔁 참고: 시스템 전체 서비스로 등록한 경우
만약 ~/Library/LaunchAgents가 아니라 /Library/LaunchDaemons/에 등록했다면, sudo 권한
필요 하며 삭제 절차도 약간 다릅니다:
sudo launchctl unload /Library/LaunchDaemons/com.user.flaskserver.plist
sudo rm /Library/LaunchDaemons/com.user.flaskserver.plist
→ 그런데 지금 사용하시는 건 유저 단위인 LaunchAgents로 보이므로 위 절차면 충분합니다.
🚨 남은 흔적 정리 팁
-
launchctl list로 남아 있는 것 전부 점검 -
/tmp/flaskserver.out,.err로그 파일도 지워도 됩니다:
rm /tmp/flaskserver.*
필요하시면 삭제 전 .plist 확인해서 위험한 설정이 남아 있는지 같이 검토해드릴 수 있습니다.
실행할 때 환경변수 설정할 수 있어?
네, launchd를 통해 실행하는 plist 파일에서 환경변수 설정 가능 합니다. 다만 일반적인 shell 환경
(.bashrc, .zshrc)과는 다르게, 명시적으로 plist 내에
<key>EnvironmentVariables</key> 항목을 추가해야 합니다.
✅ 예시: 환경변수 설정 추가한 plist
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.flaskserver</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/python3</string>
<string>/Users/yourusername/path/to/flask_app.py</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/yourusername/path/to/</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/flaskserver.out</string>
<key>StandardErrorPath</key>
<string>/tmp/flaskserver.err</string>
<!-- 환경변수 설정 -->
<key>EnvironmentVariables</key>
<dict>
<key>FLASK_ENV</key>
<string>development</string>
<key>MY_SECRET_KEY</key>
<string>s3cr3tvalue</string>
<key>PATH</key>
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>
🧠 주의사항
1. PATH 직접 설정하는 것이 중요
-
launchd는 사용자의 shell 환경을 로드하지 않음. -
PATH가 기본적으로/usr/bin:/bin으로 매우 제한적임. -
따라서
python3,venv,flask,gunicorn등을 제대로 실행하려면 명시적으로PATH추가해야 함.
2. 가상환경 경로 설정도 가능
예를 들어 가상환경을 사용하는 경우:
<key>VIRTUAL_ENV</key>
<string>/Users/yourusername/venv</string>
그리고 ProgramArguments도 가상환경 내부의 python으로 지정해야 합니다:
<string>/Users/yourusername/venv/bin/python</string>
🔁 적용 시 주의
- plist 수정 후 반드시 다음 두 명령을 다시 실행해야 함:
launchctl unload ~/Library/LaunchAgents/com.user.flaskserver.plist
launchctl load ~/Library/LaunchAgents/com.user.flaskserver.plist
그냥 저장만 하면 적용 안 됩니다.
확인 방법
환경변수가 실제 적용됐는지 Flask 내부에서 출력해보면 됩니다:
import os
print("FLASK_ENV =", os.environ.get("FLASK_ENV"))
또는 로그(/tmp/flaskserver.out)에서 확인.
필요하시면 .plist 전체 구성 예제 제공해드릴 수 있습니다. 어떤 환경변수가 필요한지 알려주시면 반영해드릴게요.
VIRTUAL_ENV key는 뭔지 잘 모르겠어. plist가 이걸 인식해?
좋은 질문입니다. 결론부터 말하자면:
VIRTUAL_ENV라는 환경변수는 Flask나 launchd가 특별히 해석하는 게 아니고 ,
그냥 일반적인 환경변수 중 하나입니다.
자동으로 가상환경이 활성화되지는 않습니다.
🔍 그럼 왜 VIRTUAL_ENV를 쓰는 예제가 있냐?
이건 다음과 같은 전제 가 있을 때만 유효합니다:
-
가상환경 내부의 Python을 실행하면서
-
그 환경 안에서 특정 스크립트가
VIRTUAL_ENV값을 참고하도록 짜여 있을 때
예를 들어 Flask 앱이 내부적으로 os.environ['VIRTUAL_ENV']를 사용해서 설정을 바꾼다거나, 로그 경로를
설정한다거나 할 때입니다.
그 외에는 아무 일도 안 생깁니다.
🧠 진짜 중요한 건 가상환경의 Python 을 직접 실행하는 것
즉, 다음처럼 ProgramArguments를 가상환경의 python으로 직접 지정해야 합니다:
<array>
<string>/Users/yourusername/venv/bin/python</string>
<string>/Users/yourusername/path/to/flask_app.py</string>
</array>
그럼 그 Python이 알아서 그 가상환경 안의 모듈 (예: Flask, requests 등)을 인식하고 실행합니다.
🧪 확인 실험
혹시라도 VIRTUAL_ENV만 설정하고 시스템 기본 Python으로 실행하면:
-
Flask 못 찾을 수도 있고
-
버전 꼬이거나
-
의존성이 깨집니다.
예시:
ModuleNotFoundError: No module named 'flask'
→ 이건 ProgramArguments가 잘못됐다는 신호입니다.
✅ 요약
| 항목 | 설명 |
|---|---|
VIRTUAL_ENV |
단순 문자열 환경변수일 뿐, launchd나 Python이 자동 인식 안 함 |
| 진짜 중요한 것 | ProgramArguments에서 가상환경 안의 python 경로 를 써야 함 |
| 권장 사용 | VIRTUAL_ENV는 특별한 이유 없으면 생략 가능 |
| PATH 문제 해결 | 가상환경 경로를 명시하거나 PATH를 직접 지정해야 함 |
필요하시면 현재 가상환경 경로나 flask_app.py 경로 기반으로 정확한 plist 샘플 구성 도와드릴 수 있습니다.
아 그러니까 EnvironmentVariable 키의 딕셔너리 안에 저걸 넣을 수 있다는 거지?
맞습니다. 정확히 짚으셨습니다.
plist에서 <key>EnvironmentVariables</key> 아래에 있는 딕셔너리는 단순히 key-value 쌍의
환경변수 목록 입니다. 예를 들어:
<key>EnvironmentVariables</key>
<dict>
<key>FLASK_ENV</key>
<string>development</string>
<key>VIRTUAL_ENV</key>
<string>/Users/yourusername/venv</string>
</dict>
이건 단순히 FLASK_ENV=development, VIRTUAL_ENV=/Users/yourusername/venv를
환경변수로 등록 하겠다는 의미입니다.
그런데 중요한 점은:
-
VIRTUAL_ENV를 저렇게 설정했다고 해서, launchd가 가상환경을 "활성화"해주지는 않습니다. -
그냥 환경변수 하나가 생긴 겁니다.
-
그래서
VIRTUAL_ENV를 여기에 넣는 건 필수도 아니고 자동 동작도 없음.
요약
| 항목 | 설명 |
|---|---|
EnvironmentVariables |
그냥 문자열 환경변수 딕셔너리 |
VIRTUAL_ENV 넣을 수 있나? |
✅ 가능함. 단, 의미는 “환경변수 하나 등록”일 뿐 |
| 가상환경 활성화되나? | ❌ 전혀 아님. 직접 venv/bin/python으로 실행해야 함 |
| 언제 유용한가? | Flask 코드 내부에서 VIRTUAL_ENV를 참조하는 경우 등 제한적 상황 |
궁금한 점 생기면 더 물어보셔도 괜찮습니다. 이거 처음엔 은근 헷갈립니다.
아, 네. 그렇게 오해할 만합니다. 구조를 보면 XML 형식이 마치 설정 키워드처럼 생겼으니까요.
그런데 <key>VIRTUAL_ENV</key>처럼 쓰는 건 전혀 다른 맥락입니다.
그건 plist 문법상 완전히 잘못된 사용 입니다.
🔍 왜 헷갈리는가?
plist 형식은 대략 이런 식입니다:
<key>SomeKey</key>
<valueType>...</valueType>
예를 들어:
<key>KeepAlive</key>
<true/>
→ 이건 macOS가 인식하는 정의된 설정 키 입니다. KeepAlive, RunAtLoad, Label 같은 것들은
macOS의 launchd가 해석하는 예약된 키입니다.
❌ 잘못된 예 (이건 안 됨)
<key>VIRTUAL_ENV</key>
<string>/some/path</string>
→ 이런 식으로 plist 최상단 dict 안에 VIRTUAL_ENV를 그냥 넣으면,
launchd는 그게 뭔지 모릅니다.
무시되거나 에러 날 수도 있습니다.
✅ 올바른 방식
모든 커스텀 환경변수는 반드시 아래처럼EnvironmentVariables 안에 넣어야 합니다:
<key>EnvironmentVariables</key>
<dict>
<key>VIRTUAL_ENV</key>
<string>/Users/yourusername/venv</string>
</dict>
이 방식만이 launchd가 환경변수로 인식하고 자식 프로세스(여기선 Flask 앱)에 전달해주는 유일한 방법입니다.
요약
| 구문 | 의미 | 유효 여부 |
|---|---|---|
<key>KeepAlive</key><true/> |
macOS 예약 키 | ✅ |
<key>RunAtLoad</key><true/> |
macOS 예약 키 | ✅ |
<key>VIRTUAL_ENV</key><string>...</string> |
의미 없음, 오용 | ❌ |
<key>EnvironmentVariables</key><dict>...</dict> |
사용자 환경변수 정의 | ✅ |
결론:
<key>VIRTUAL_ENV</key> 같은 건 launchd가 전혀 모릅니다.
모든 사용자 정의 값은 반드시 EnvironmentVariables 안에서 정의해야 합니다.
추가로 plist를 검증하거나 문법 오류를 확인하려면:
plutil ~/Library/LaunchAgents/com.user.flaskserver.plist
이렇게 하면 구문 오류도 잡아낼 수 있습니다.
응 너가 그냥
그렇습니다. 지적 정확하십니다.
그 표현은 확실히 오해를 유발할 수 있었습니다.
제가 그걸 EnvironmentVariables 딕셔너리 안의 한 항목 이라는 맥락 없이 따로 언급했기 때문에,
마치 <key>KeepAlive</key> 같은 예약된 상위 키처럼 보였을 수밖에 없습니다.
👇 명확하게 다시 정리하자면
<key>EnvironmentVariables</key>
<dict>
<key>VIRTUAL_ENV</key>
<string>/Users/yourusername/venv</string>
</dict>
이 구조 안에서만 VIRTUAL_ENV는 의미가 있고,
그 외의 위치에서는 아무 효과도 없습니다.
🛠 다시 쓴다면 이렇게 말했어야 했습니다
EnvironmentVariables딕셔너리 안에VIRTUAL_ENV라는 키를 정의할 수 있습니다.
하지만 이는 단순한 문자열 환경변수일 뿐이고, launchd가 특별히 인식하는 설정 키는 아닙니다.
🧩 요약: 제 표현이 헷갈리게 만든 건 맞습니다. 날카로운 피드백 감사합니다.
이런 지적은 오히려 정확한 설명을 만드는 데 꼭 필요합니다.
근데 파인더에서 홈에서 Library 폴더가 안보여. 새로 만드려고하니 이미 있대
맞습니다. macOS는 기본적으로 ~/Library 폴더를 숨김 처리 해놓습니다. 그래서 Finder에서는 안 보이지만 실제로는
존재하고 있고, 시스템이나 앱 설정 파일들이 여기에 많이 들어 있습니다.
✅ 이미 존재한다는 메시지의 의미
"이미 있음"이라는 건 실제로
~/Library디렉터리가 숨겨진 상태로 존재 중 이라는 뜻입니다.
따라서 그냥 Finder에서 못 보이는 것뿐이지, 정상입니다.
🔓 Finder에서 ~/Library 열기 (3가지 방법)
🔹 방법 1: Finder에서 직접 열기 (단축키)
-
Finder를 연 상태에서
-
Shift + Command + G(이동 > 폴더로 이동) 클릭 - 아래 입력:
~/Library - Enter
→ 그러면 숨김 상태여도 바로 열립니다.
🔹 방법 2: 터미널에서 열기
open ~/Library
→ Finder에서 바로 열립니다.
🔹 방법 3: 영구적으로 보이게 하기
-
~/(홈 디렉터리) 열기 -
Command + Shift + .누르기
(숨김 파일/폴더 표시 토글 단축키)
→ 그러면 .gitignore, .zshrc, Library 등 숨겨진 항목이 보입니다.
다시 누르면 숨깁니다.
📁 LaunchAgents 폴더도 없을 수 있음
~/Library/LaunchAgents/가 없는 경우도 있습니다. 이때는 직접 생성하셔야 합니다:
mkdir -p ~/Library/LaunchAgents
→ 경로가 정확히 맞아야 launchctl이 인식합니다.
✅ 요약
| 작업 | 방법 |
|---|---|
~/Library 열기 |
Shift + Command + G → ~/Library |
| 숨김 폴더 보기 토글 | Command + Shift + . |
| 직접 열기 | open ~/Library in 터미널 |
| LaunchAgents 없을 때 | mkdir -p ~/Library/LaunchAgents |
필요하시면 LaunchAgents에 올릴 plist 파일 위치도 같이 확인해드릴 수 있습니다.
너가 준 예제에서 flaskserver 부분은 임의로 바꿔도 되는거지?
네, 맞습니다. flaskserver라는 문자열은 사용자가 임의로 정해도 되는 이름 입니다. 다만 바꿀 수는 있지만 함부로
바꾸면 추적이 어려워질 수 있으니 주의점이 몇 가지 있습니다.
🔍 바꿀 수 있는 부분
예를 들어 아래 예제에서:
<key>Label</key>
<string>com.user.flaskserver</string>
...
<key>StandardOutPath</key>
<string>/tmp/flaskserver.out</string>
<key>StandardErrorPath</key>
<string>/tmp/flaskserver.err</string>
-
com.user.flaskserver→ 당신이 정한 서비스 이름 -
flaskserver.out,flaskserver.err→ 로그 파일 이름
✅ 전부 다 원하는 이름 으로 바꿀 수 있습니다.
💡 주의해야 할 점
항목| 설명
—|—
Label| launchctl에서 서비스를 식별하는 고유한 이름 입니다. 이걸로 start, stop, list
명령에서 찾습니다.
.plist 파일 이름| 반드시 .plist 확장자 유지해야 하지만, 파일명 자체는 자유롭게 정할 수 있습니다. 예:
com.myapp.webapi.plist
로그 파일 경로| /tmp/flaskserver.out도 원하는 경로와 이름으로 변경 가능. 단, 쓰기 권한이 있는 경로 여야
함.
💬 예시: 이름을 바꾼 경우
파일명:
~/Library/LaunchAgents/com.myproject.webapi.plist
내용 일부:
<key>Label</key>
<string>com.myproject.webapi</string>
<key>StandardOutPath</key>
<string>/tmp/webapi.out</string>
<key>StandardErrorPath</key>
<string>/tmp/webapi.err</string>
실행/확인 명령:
launchctl load ~/Library/LaunchAgents/com.myproject.webapi.plist
launchctl start com.myproject.webapi
launchctl list | grep com.myproject.webapi
🧠 권장 네이밍 규칙
-
com.username.appname또는org.company.projectname같이 도메인 스타일 이 일반적입니다. -
그래야 충돌 가능성이 적고 명확합니다.
예:
-
com.junho.flaskapi -
com.localserver.devserver -
org.me.webhook_listener
결론적으로, flaskserver는 그냥 예제일 뿐이고 전부 자유롭게 바꿀 수 있지만 ,
Label 값과 파일명은 일관되게 맞춰줘야 관리가 편합니다.