파일 저장소
모든 앱 데이터 백엔드는 테이블과 함께 비공개 객체 저장소를 제공받습니다. 데이터 백엔드가 시계열 행을 담는다면, 파일 저장소는 행에 담을 수 없는 모든 것을 담습니다: 카메라 프레임, PDF 리포트, 펌웨어 바이너리, 오디오 클립, 내보내기.
이 둘은 함께 사용하도록 설계되었습니다. 파일을 저장하면 그 즉시 영구 URL을 돌려받으며, 의도된 패턴은 그 URL을 곧바로 테이블 컬럼에 기록하는 것입니다 — 그러면 대시보드 위젯이 추가 작업 없이 이미지를 렌더링합니다:
Python
info = await ironflock.files.put("part-1.jpg", jpeg_bytes, content_type="image/jpeg")
await ironflock.publish_to_table("inspections", part_id="1", photo_url=info.url)전체 클라이언트 API — 읽기, 목록 조회, 사용량, 공유 링크, 대용량 객체, 오류 코드 — 는 SDK 참조에 문서화되어 있습니다. 이 페이지는 플랫폼 측면을 다룹니다: 저장소가 어떻게 선언되고, 관리되고, 공유되고, 운영되는지.
작동 방식
- 선택적으로
.ironflock/data-template.yml에files:섹션을 선언합니다. - 사용자가 프로젝트에 앱을 설치하면, IronFlock이 프로젝트 데이터베이스를 프로비저닝하는 것과 똑같이 앱을 위한 비공개 저장 영역을 프로비저닝합니다.
- 엣지 코드는 SDK의
filesAPI를 통해 객체를 저장하고 읽습니다. - 앱을 제거하면 저장소가 완전히 삭제됩니다: 데이터베이스 스키마가 삭제되는 것과 마찬가지로 모든 객체와 모든 자격 증명이 사라집니다.
데이터베이스와 마찬가지로 저장소는 프로젝트별입니다. 같은 앱을 두 프로젝트에 설치하면 완전히 분리된 두 개의 저장 영역이 생기며, 앱 개발자인 여러분은 둘 중 어느 것에도 접근할 수 없습니다 — 데이터는 앱을 실행하는 사용자의 것입니다.
설정이 전혀 없어도 유효한 구성입니다: files: 섹션이 없는 앱도 default라는 이름의 네임스페이스를 하나 갖게 되므로, 모든 앱에서 files.put(...)이 별도의 설정 없이 바로 작동합니다.
데이터 템플릿에서 저장소 선언
files: 섹션은 data-template.yml에서 data: 옆에 위치합니다:
files:
description: Camera frames and generated inspection reports.
# 앱이 스스로 제안하는 저장 예산, 바이트 단위 (여기서는 5 GiB).
# 프로젝트 사용자가 이를 재정의할 수 있으며, 사용자의 설정이 강제 적용됩니다.
quotaBytes: 5368709120
namespaces:
- name: frames
description: Raw camera frames, one JPEG per inspected part.
contentTypes: ["image/jpeg"]
maxObjectBytes: 20971520
retention: { deleteAfter: 30 days }
- name: reports
description: Generated PDF inspection reports.
contentTypes: ["application/pdf"]
private: true네임스페이스
네임스페이스는 정책을 담고 있는 키 접두사입니다. 별도의 저장 버킷이 아닙니다 — 앱의 모든 네임스페이스는 해당 앱의 단일 저장 영역 안에 존재하며, 네임스페이스 이름은 단순히 각 객체 저장 경로의 첫 번째 세그먼트가 됩니다.
이 구분이 언제 네임스페이스를 선언해야 하는지 알려줍니다:
- 파일을 정리하고 싶나요?
default네임스페이스 안에서 키 경로 —2026/03/part-1.jpg— 를 사용하십시오. 폴더 스타일 키가 일반적인 경우입니다. - 어떤 객체 집합에 다른 규칙이 필요한가요? 네임스페이스를 선언하십시오. 규칙이 네임스페이스가 추가하는 유일한 것입니다.
네임스페이스가 담을 수 있는 규칙:
| 필드 | 의미 |
|---|---|
name | 소문자, 숫자, 하이픈으로 구성되며 문자로 시작합니다. sys, system, ironflock, ironflock-*는 예약되어 있습니다 |
description | 사용자에게 표시되며 AI 에이전트가 읽을 수 있습니다 |
private | 네임스페이스를 크로스 앱 접근에서 제외합니다. 기본값은 false(공유) — 테이블과 완전히 동일합니다 |
contentTypes | 허용되는 MIME 유형이며, 글롭을 사용할 수 있습니다(image/*). 기본값: 모두 |
maxObjectBytes | 단일 객체의 최대 크기이며, 최대 5 GiB까지 가능합니다. 기본값 100 MiB |
retention.deleteAfter | 이 기간보다 오래된 객체는 자동으로 삭제됩니다(30 days, 2 weeks, 1 year, …) |
저장 예산
quotaBytes는 네임스페이스별이 아니라 앱 전체에 대해 한 번만 선언됩니다. 네임스페이스는 키 접두사일 뿐이므로 접두사별 예산을 강제할 대상 자체가 존재하지 않습니다 — 할당량은 앱의 단일 저장 영역에 적용되며, 직접 업로드를 포함한 모든 쓰기 경로에서 객체 저장소가 직접 이를 강제합니다.
두 개의 숫자가 존재하며, 그 차이는 의도적입니다:
- 제안된 할당량 — 템플릿이 요청하는 값입니다. 앱이 처음 설치될 때 적용됩니다.
- 강제되는 할당량 — 프로젝트 사용자가 설정한 값입니다. 사용자가 앱의 저장소 설정에서 예산을 변경하면 사용자의 값이 우선하며, 앱을 재배포해도 이 값은 초기화되지 않습니다.
템플릿에 아무것도 명시하지 않으면 플랫폼 기본값이 적용됩니다(개발 백엔드는 1 GiB, 프로덕션 백엔드는 10 GiB).
보존 기간
네임스페이스의 deleteAfter 기간을 넘긴 객체는 자동으로 제거됩니다 — 테이블의 dropAfter 정책에 해당하는 파일 측 대응물입니다. 보존은 객체 저장소가 이를 지원하는 경우 저장소 내부에서 실행되고, 지원하지 않는 경우(온프레미스 어플라이언스)에는 매일 실행되는 플랫폼 작업으로 실행되므로, 선언된 보존 기간은 어디서나 동일하게 동작합니다.
영구 URL과 대시보드
저장된 모든 객체는 https://files.ironflock.com/f/<backend>/<namespace>/<key> 형식의 안정적인 URL을 가집니다. 세 가지 속성이 이를 테이블 컬럼에 기록하기 적합한 것으로 만듭니다:
- 절대 만료되지 않습니다. 이 URL은 순수한 주소이며, 객체가 존재하는 동안 계속 유효합니다.
- 공개 링크가 아닙니다. 모든 요청은 요청자가 로그인되어 있고 이 데이터 백엔드에 대한 READ 접근 권한을 가지고 있는지 확인하는 인증 프록시를 거칩니다 — 매 요청마다 다시 확인됩니다. 사용자의 접근 권한을 철회하면 그 즉시 모든 파일을 가져올 수 있는 능력이 함께 철회됩니다.
<img>에서 렌더링됩니다. 브라우저가 세션 쿠키를 자동으로 전송하므로, 대시보드 위젯은 JavaScript 없이도<img src>,<video src>또는 다운로드 링크에 이 URL을 사용할 수 있습니다.
프로젝트 외부의 누군가에게 파일을 건네주려면, SDK의 share_url이 대신 만료되는 소지자 링크를 발급합니다 — 어느 것을 언제 사용할지는 객체 공유를 참조하십시오.
대용량 파일
6 MiB까지의 전송은 메시징 시스템을 통해 단일 호출로 이동합니다. 그보다 큰 것은 HTTPS를 통해 디바이스와 객체 저장소 간에 직접 전송됩니다 — SDK가 자동으로 전환하고, 디스크에서 스트리밍하며, 수 기가바이트짜리 파일도 메모리에 담길 필요가 전혀 없습니다. 단일 업로드 상한은 5 GiB입니다.
직접 경로에서는 디바이스가 메시징 라우터뿐 아니라 객체 저장소 호스트(s3.ironflock.com)에도 도달할 수 있어야 합니다. 공장 프록시가 라우터만 허용하는 경우, 대용량 전송은 일반적인 오류가 아니라 명시적인 코드 PRESIGN_UNREACHABLE와 함께 실패합니다 — 그리고 디바이스 시계가 15분 넘게 어긋나 있으면 CLOCK_SKEW와 함께 실패하는데, 이는 자격 증명이 아니라 NTP를 확인하라는 신호입니다.
앱 간 파일 공유
크로스 앱 파일 접근은 크로스 앱 테이블 접근과 동일한 동의를 따릅니다. 스위치는 하나입니다: 프로젝트 사용자가 앱 B에 앱 A의 데이터 접근 권한을 부여하면(앱 설정의 data_access 동의), 그 권한 부여는 A의 테이블 과 A의 비공개가 아닌 파일 네임스페이스를 모두 포함합니다. 이를 철회하면 둘 다 철회됩니다.
제공자로서 앱이 제어하는 것은 네임스페이스별 private 플래그입니다:
private: false(기본값) — 데이터 접근 권한을 가진 앱이 이를 읽을 수 있습니다(쓰기는 절대 불가).private: true— 권한이 부여되어 있어도 네임스페이스는 다른 앱에 전혀 보이지 않습니다.
이는 다른 앱의 데이터 사용과 정확히 동일합니다: 네임스페이스와 테이블의 기본값은 같습니다. 프로젝트 사용자의 권한 부여 없이는 아무것도 공유되지 않으며, private:는 이미 동의를 받은 읽기 주체가 보는 범위를 좁힐 뿐 동의 자체가 아닙니다.
사용자의 관점
프로젝트 사용자는 두 곳에서 앱의 저장소를 확인하고 관리합니다:
- 데이터 뷰는 모든 앱의 테이블 및 뷰 옆에 파일 항목을 표시합니다 — 크기, 유형, 수정 날짜와 함께 저장된 모든 객체를 검색 가능한 목록으로 보여주며, 파일별 다운로드를 제공합니다.
- 앱의 저장소 설정은 사용량(바이트 및 객체 개수), 앱의 제안 옆에 표시되는 강제되는 예산, 예산을 변경하는 컨트롤, 그리고 모든 파일 삭제 작업을 보여줍니다 — 모든 테이블을 비우는 것의 파일 측 쌍둥이입니다. 삭제는 앱 이름을 입력하여 확인하며 되돌릴 수 없습니다.
- AI 어시스턴트는 사용자를 대신해 이 파일들을 나열, 검색, 읽기할 수 있습니다. 동일한
DATABACKEND/READ권한 검사가 적용되므로 사용자가 직접 열 수 없는 파일은 절대 나타나지 않습니다. 파일 내용이 아니라 경로를 검색하며, 텍스트 파일과 이미지, PDF를 읽습니다. 압축 파일 등 다른 바이너리 형식은 읽을 수 없습니다.
테이블과 마찬가지로 이것은 사용자의 데이터입니다: 사용자는 여러분을 거치지 않고도 이를 점검하고, 상한을 두고, 삭제할 수 있습니다.
직접 S3 접근
SDK를 벗어난 모든 경우 — DuckDB를 쓰는 분석가, 야간 rclone 백업, BI 파이프라인 — 에 대해, 프로젝트 사용자는 앱의 저장소 설정에서 앱별 읽기 전용 S3 자격 증명을 발급할 수 있습니다.
각 자격 증명은 그 하나의 앱 저장 영역으로 범위가 제한됩니다: 앱 A용으로 발급된 자격 증명은 구조적으로 앱 B의 파일에는 작동하지 않습니다 — B의 저장소 정책이 애초에 이를 언급하지 않기 때문입니다. 프로젝트는 앱별로 여러 개의 자격 증명을 보유할 수 있으며(소비자당 하나씩: CI 작업, 백업 스크립트, 노트북), 하나를 철회해도 나머지는 계속 작동합니다. 이것이 곧 키 교체 방법이기도 합니다: 키가 유출되면 두 번째 자격 증명을 발급하고, 소비자를 그쪽으로 옮긴 다음, 첫 번째를 철회하면 됩니다 — 다른 어떤 소비자도 방해받지 않습니다.
시크릿은 생성 시점에 한 번만 표시됩니다. 발급하려면 해당 앱 데이터 백엔드에 대한 읽기 권한이 필요합니다 — 애초에 파일을 읽을 수 있게 해 주는 것과 동일한 권한이므로, 자격 증명이 누군가의 접근 범위를 넓히는 일은 결코 없습니다.
# rclone
rclone config create myapp s3 provider=Other \
endpoint=https://s3.ironflock.com \
access_key_id=ifs-key-3317-x7k2m secret_access_key=<shown once>
rclone ls myapp:if-1042-3317# DuckDB
CREATE SECRET (TYPE S3, KEY_ID 'ifs-key-3317-x7k2m', SECRET '<shown once>',
ENDPOINT 's3.ironflock.com');
SELECT * FROM read_parquet('s3://if-1042-3317/exports/*.parquet');저장소 설정은 클라이언트가 가리킬 엔드포인트와 정확한 버킷 이름을 보여줍니다. 최상위 버킷 목록 조회(인수 없는 aws s3 ls)는 설계상 아무것도 반환하지 않는다는 점에 유의하십시오 — 자격 증명은 어떤 버킷도 소유하지 않으며, 하나에 대한 접근 권한을 부여받을 뿐입니다. 위와 같이 버킷을 직접 지정하십시오.
온프레미스 어플라이언스
파일 저장소는 온프레미스 어플라이언스에서도 동일하게 작동하며, 어플라이언스의 설계에서 비롯되는 세 가지 차이가 있습니다:
- 파일은 어플라이언스 자체 주소(
/files/...) 아래에서 **동일 출처(same-origin)**로 제공됩니다 — 추가 DNS 이름도, 추가 인증서도 필요 없으며, 순수 HTTP 설치에서도 작동합니다. 영구 URL과<img src>는 클라우드에서와 똑같이 동작합니다. - 어플라이언스의 저장소 백엔드는 별도의 저장소 호스트로 리디렉션하는 대신 플랫폼을 통해 파일 다운로드를 스트리밍하므로, 저장소 서비스가 두 번째 출처로 노출되는 일이 없습니다.
- 어플라이언스에서는 직접 S3 자격 증명을 사용할 수 없습니다 — 내장된 객체 저장소가 자격 증명별 접근 권한 부여를 표현할 수 없기 때문입니다. 해당 섹션은 그곳의 저장소 설정에 아예 나타나지 않습니다. SDK를 통한 대용량 파일 동작과 선언된 보존 기간을 포함한 그 밖의 모든 것은 동일하게 작동합니다.
망 분리된 어플라이언스는 모든 파일 트래픽을 로컬에서 제공합니다: 앱 로고, 대시보드 이미지, 파일 다운로드에 인터넷 연결이 필요하지 않습니다.
한눈에 보는 제한
| 제한 | 값 | 출처 |
|---|---|---|
| 인라인 전송(단일 호출) | 6 MiB | 런타임에 보고되며, 서버 측에서 상향될 수 있습니다 |
| 단일 객체 | 기본 100 MiB, 최대 5 GiB | 네임스페이스별 maxObjectBytes |
| 단일 업로드 | 5 GiB | 객체 저장소의 단일 PUT 상한이며, 멀티파트는 아직 제공되지 않습니다 |
| 저장 예산 | 기본값 개발 1 GiB / 프로덕션 10 GiB | 템플릿이 제안하고, 사용자가 결정합니다 |
| 키 길이 및 문자 | UTF-8, /로 구분된 경로 | ../, 제어 문자, 예약된 접두사는 거부됩니다 |