문서 본문으로 건너뛰기
솜사탕 Docs구성
가이드준비 중업데이트 2026-08-01기능 검증 2026-07-24

파일 업로드와 영구 보관

이미지·영상·첨부파일을 재배포 뒤에도 안전하게 보관하는 방법과 현재 플랫폼 경계를 설명합니다.

이 문서에서 다루는 내용

로컬 폴더는 영구 보관소가 아닙니다

uploads 폴더의 파일은 사라질 수 있습니다
실행 중인 앱이 uploads/, public/uploads/,/tmp 같은 컨테이너 로컬 경로에 만든 파일은 재배포, 재시작, Pod 교체 또는 서버 이동 뒤 유지되지 않을 수 있습니다. 한동안 파일이 보였더라도 영구 보관이 보장된 것은 아닙니다.
  • 저장소에 포함된 로고·아이콘 같은 정적 파일은 GitHub에 커밋해 새 빌드에도 포함합니다.
  • 사용자가 업로드한 이미지·영상·문서는 외부 object storage에 보관합니다.
  • 변환 중인 파일과 다시 만들 수 있는 캐시만 로컬 임시 경로에 둡니다.
  • 중요 파일이 이미 로컬 폴더에만 있다면 새 배포나 서버 교체 전에 먼저 내려받아 별도로 보관합니다.

데이터에 맞는 저장소 선택

저장 방식
PostgreSQL
적합한 데이터
회원, 주문, 게시글, 권한처럼 검색·관계·트랜잭션이 필요한 구조화 데이터
솜사탕에서의 현재 기준
관리형 DB로 제공됩니다. 큰 파일 원본 보관소로 사용하지 않습니다.
저장 방식
컨테이너 로컬 폴더
적합한 데이터
재생성 가능한 캐시, 빌드·변환 중 임시 파일
솜사탕에서의 현재 기준
재배포·재시작·Pod 교체 뒤 보존을 보장하지 않습니다.
저장 방식
영구 Volume
적합한 데이터
POSIX 파일시스템이 꼭 필요한 단일 앱의 상태 파일
솜사탕에서의 현재 기준
현재 일반 고객이 추가하는 별도 리소스로 제공하지 않습니다.
저장 방식
Object storage
적합한 데이터
이미지, 영상, PDF와 사용자 첨부파일처럼 URL·API로 읽고 쓰는 파일
솜사탕에서의 현재 기준
현재 외부 서비스를 환경변수로 연결합니다. 솜사탕 관리형 Bucket은 아직 별도 리소스로 제공하지 않습니다.

DB에는 파일 원본 대신 object key, 소유자, 파일명, 크기, MIME type과 생성 시각 같은 메타데이터를 저장하는 구성이 일반적입니다. 만료되는 서명 URL 자체를 영구 데이터로 저장하지 마세요.

외부 object storage를 안전하게 연결하기

현재 제공 범위
솜사탕클라우드는 관리형 파일 스토리지나 Bucket을 현재 별도 리소스로 제공하지 않습니다. 당분간 S3 호환 스토리지 또는 사용 중인 외부 object storage를 서비스 환경변수로 연결해 사용하세요. 저장 용량, 전송 비용, 백업·복구와 데이터 보관 위치는 선택한 제공자의 정책을 확인해야 합니다.
  1. 1
    비공개 Bucket을 만듭니다
    전체 공개 Bucket보다 필요한 파일에만 제한된 접근을 부여하는 구성을 권장합니다.
  2. 2
    접속 정보는 서버 전용 환경변수에 저장합니다
    access key와 secret은 브라우저 번들, GitHub 저장소, 앱 로그에 넣지 않습니다.
  3. 3
    서버가 짧게 만료되는 presigned URL을 만듭니다
    로그인과 파일 소유권을 확인한 뒤 업로드·다운로드에 필요한 작업과 경로만 허용합니다.
  4. 4
    브라우저는 서명 URL로 파일을 전송합니다
    비밀키는 노출하지 않고 큰 파일이 앱 서버를 불필요하게 통과하는 것도 줄일 수 있습니다.
  5. 5
    DB에는 object key와 메타데이터를 기록합니다
    사용자 권한, 삭제 상태와 파일 정보를 앱 데이터와 연결하고 실제 파일 lifecycle과 함께 관리합니다.

서버 전용 설정과 presigned URL 패턴

환경변수 키 예시 · dotenv
OBJECT_STORAGE_ENDPOINT=https://STORAGE_ENDPOINT
OBJECT_STORAGE_REGION=REGION
OBJECT_STORAGE_BUCKET=BUCKET_NAME
OBJECT_STORAGE_ACCESS_KEY_ID=ACCESS_KEY_ID
OBJECT_STORAGE_SECRET_ACCESS_KEY=SECRET_ACCESS_KEY
브라우저 공개 접두사를 붙이지 마세요
credential 변수에 NEXT_PUBLIC_ 또는 VITE_같은 공개 접두사를 붙이면 브라우저 코드에 포함될 수 있습니다. 비밀키는 서버 route나 server component에서만 읽으세요.
서버 route의 개념 예시 · typescript
// 로그인과 파일 소유권을 먼저 확인합니다.
const objectKey = `uploads/${user.id}/${crypto.randomUUID()}`;

// 선택한 storage SDK로 5분짜리 업로드 URL을 만듭니다.
const uploadUrl = await createPresignedUploadUrl({
  objectKey,
  contentType,
  expiresInSeconds: 300,
});

return Response.json({ objectKey, uploadUrl });
브라우저 업로드 예시 · typescript
await fetch(uploadUrl, {
  method: "PUT",
  headers: { "Content-Type": file.type },
  body: file,
});

createPresignedUploadUrl은 설명을 위한 이름입니다. 실제 구현은 선택한 storage 제공자의 공식 SDK와 권한 정책을 사용하고, 서버에서 사용자·파일 경로·크기·형식을 검증하세요.

출시 전 파일 보관 체크리스트

  • Bucket은 기본 비공개로 두고 앱 전용 credential에는 필요한 경로와 작업만 허용합니다.
  • 업로드 허용 형식, 최대 크기, 사용자별 권한과 object key 생성 규칙을 서버에서 검증합니다.
  • 브라우저 직접 업로드를 사용하면 허용 origin과 method만 열도록 CORS를 설정합니다.
  • presigned URL의 만료 시간을 짧게 두고 URL이 로그·분석 도구·지원 메시지에 남지 않게 합니다.
  • 파일 삭제, 계정 탈퇴, export, 백업·버전 관리와 복구 가능 범위를 정합니다.
  • 파일을 업로드한 뒤 앱을 재배포하거나 Pod가 교체돼도 같은 파일을 다시 읽을 수 있는지 검증합니다.

지금 로컬 파일을 사용 중이라면

  1. 1
    중요 파일을 먼저 보관합니다
    새 배포를 시작하기 전에 현재 필요한 파일을 내려받거나 외부 저장소로 옮깁니다.
  2. 2
    새 업로드를 object storage로 전환합니다
    앱 코드는 파일 원본 대신 object key를 저장하고 필요할 때 서명 URL을 발급하도록 바꿉니다.
  3. 3
    재배포 뒤 읽기·삭제까지 확인합니다
    업로드 성공만 보지 말고 로그인 사용자별 접근, 다운로드, 삭제와 오류 흐름을 함께 점검합니다.