2026년 11월 13일 금요일

Portainer git 스택에서 지운 파일이 이미지에 다시 들어가는 문제

저장소에서 분명히 지운 파일이 배포된 컨테이너 안에서 멀쩡히 돌고 있었다. Portainer 의 git 스택이 저장소를 "복사"만 하고 "동기화"하지는 않기 때문이었다.

증상

Node 앱에서 더 이상 쓰지 않는 수집 모듈 하나를 저장소에서 삭제하고 push 했다. Portainer 스택은 정상적으로 재배포됐고 로그에도 오류가 없었는데, 지운 모듈이 계속 실행되면서 이미 없앤 외부 API 를 호출하고 있었다.

원인

Portainer 의 git 스택은 저장소를 자기 데이터 볼륨 안의 폴더로 내려받아 거기서 docker compose 를 실행한다. 내 환경에서는 이런 경로였다.

/var/lib/docker/volumes/portainer_data/_data/compose/<스택 ID>/

이 폴더에는 .git 이 없다. 즉 git 작업 폴더가 아니라 파일을 덮어쓴 복사본이다. 새 커밋을 받아 올 때 추가·수정된 파일은 덮어쓰지만, 저장소에서 삭제된 파일은 그대로 남는다.

여기에 두 가지가 겹치면 사고가 난다.

  • Dockerfile 이 COPY . . 나 COPY collectors/ ./collectors/ 처럼 디렉터리째 복사한다 → 남아 있던 파일이 이미지에 다시 들어간다.
  • 앱이 디렉터리를 스캔해서 모듈을 자동 로드한다 → 다시 들어온 파일이 그대로 실행된다.

해결

우선 서버에서 남은 파일을 직접 지우고 다시 빌드했다.

sudo rm /var/lib/docker/volumes/portainer_data/_data/compose/<스택 ID>/collectors/old-module.js

재발을 막으려고 두 가지를 바꿨다.

  • Dockerfile 의 COPY 를 명시 목록으로. 디렉터리 전체가 아니라 필요한 파일·폴더만 적는다.
  • 모듈 로드를 명시 목록으로. readdirSync 로 폴더를 훑는 대신 로드할 모듈 이름을 배열로 적는다. 파일이 남아 있어도 목록에 없으면 실행되지 않는다.
// 전: 폴더에 있는 건 전부 로드
// fs.readdirSync('./collectors').forEach(f => require('./collectors/' + f));

// 후: 적어 둔 것만 로드
const COLLECTORS = ['weather', 'news', 'calendar'];
COLLECTORS.forEach(name => require(`./collectors/${name}`));

정리

"배포 성공"은 "저장소와 같은 상태"를 뜻하지 않는다. git 스택의 체크아웃은 삭제를 따라가지 않으므로, 파일을 지우는 커밋을 배포할 때는 서버 쪽 복사본도 같이 확인해야 한다. 자동 스캔은 편하지만 이런 상황에서는 무엇이 실행되는지 코드만 봐서는 알 수 없게 만든다.

댓글 없음:

댓글 쓰기

반도체 주식을 보기 전에 그려 두는 지도 — 설계, 파운드리, 메모리, 장비, 소재, 후공정

"반도체주"라고 한 묶음으로 부르지만, 그 안의 회사들은 하는 일도 돈 버는 방식도 전혀 다르다. 같은 뉴스에 어떤 회사는 오르고 어떤 회사는 내리는 이유가 여기에 있다. 종목을 들여다보기 전에 머릿속에 그려 두면 좋은 지도를 정리한...