Skip to main content

ADR-010: Migrate Plane file storage from bundled MinIO to AWS S3

Context​

Plane CE is the central workflow hub for Aucert, with all task management and agent orchestration workflows running through it. File uploads (attachments, images, cover photos) currently live on a bundled MinIO pod with a 5Gi PVC inside AKS — no backup, no encryption at rest, default credentials (admin/password), HTTP-only transport. Data loss is unrecoverable.

Two migration attempts were made and failed:

Azure Blob Storage (tested 2026-04-03): Standard StorageV2 accounts have zero S3 API compatibility. boto3 presigned URLs rejected with AuthenticationFailed. S3 compat only available on Data Lake Gen2 (HNS enabled).

Cloudflare R2 (tested 2026-04-04): R2 is S3-compatible for most operations (GET, PUT, LIST) but does not support S3 POST Object (form-based uploads) — returns HTTP 501. Plane CE v1.2.0 uses generate_presigned_post() for all file uploads, making R2 incompatible without patching Plane's source code.

Key technical finding from R2 attempt: Plane's S3Storage class (/code/plane/settings/storage.py) hardcodes AWS_QUERYSTRING_AUTH=False and AWS_DEFAULT_ACL=public-read via django-storages inheritance. The class has custom generate_presigned_post() and generate_presigned_url() methods that use boto3 directly. The url() method returns raw object names (not presigned URLs). These are not configurable via environment variables.

Decision​

Migrate to AWS S3 — the only tested-compatible option that supports the full S3 API including POST Object.

Status: proposed — pending AWS account setup and IAM credential creation.

Alternatives considered​

OptionProsCons
AWS S3 (proposed)Full S3 API including POST Object, industry standard, aligns with potential AWS infrastructure migrationEgress fees (negligible for internal tool), cross-cloud traffic from Azure AKS
Keep bundled MinIONo migration effortNo backup, no encryption, data loss on cluster failure — unacceptable
Azure Blob StorageSame cloud providerTested, failed — zero S3 API compatibility
Cloudflare R2Zero egress, S3-compatible for GET/PUTTested, failed — no POST Object support (501), incompatible with Plane's upload flow
Azure Data Lake Gen2 (HNS)S3-compatibleRequires new storage account, overcomplicated
Patch Plane source code for R2Makes R2 workFragile, breaks on Plane upgrades, significant effort

Consequences​

What becomes easier​

  • Durable storage with 11 nines availability
  • Encryption at rest (SSE-S3 or SSE-KMS)
  • Scoped IAM credentials per bucket
  • Aligns with potential AWS infrastructure migration

What becomes harder​

  • Cross-cloud traffic (Azure AKS → AWS S3) adds latency
  • Two cloud providers to manage credentials for
  • S3 egress costs (minimal for internal tool with small team)

Risks​

  • If AWS infrastructure migration doesn't happen, S3 becomes a permanent cross-cloud dependency. Mitigation: egress costs are negligible for an internal tool, and S3 is the most portable storage API.