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.