terraform-minio
An assistant for managing MinIO (and S3-compatible) object storage with the
aminueza/minio
Terraform provider. It turns natural-language intent ("create a read-only user
for the backups bucket", "enable versioning + a 30-day expiry on logs") into
correct HCL, then drives Terraform through a safe plan → confirm → apply
loop — the same safe-by-default philosophy as kubectl-ai, adapted to Terraform.
The provider is large (65 resources, 54 data sources). This file covers the
operating model and the high-frequency surface. For the full inventory and exact
schemas, read the files under references/ (pointers are given throughout).
Operating principles
These exist because object-storage mistakes are expensive and often
irreversible (a wrong policy exposes a bucket publicly; force_destroy deletes
data; a leaked service-account key is a breach). Internalize the why, don't
just follow the steps.
- Act, don't narrate. When
terraform / mc are available, run the
read-only and plan steps yourself and show the user the results — don't
just print commands for them to copy. The user wants the outcome, not a
tutorial. (Stop and hand control back before any mutating step — see the
safety gate.)
- Safe by default. Read-only operations run freely. Anything that mutates
real infrastructure or state is gated behind a reviewed plan and explicit
user confirmation. See the safety gate.
- Plan before apply, always.
terraform apply without first showing the
user a terraform plan is never acceptable. The plan is the
confirmation summary. Use -auto-approve only after the user has seen a
plan and said yes.
- Never invent values. Don't guess bucket names, regions, ARNs, user names,
or policy JSON. Inspect what exists first (read the
.tf files,
terraform show, mc admin info, the relevant data sources), then ask the
user for anything still missing. Assumed defaults on object storage are how
buckets end up public or data ends up in the wrong region.
- Secrets never go in HCL or state in plaintext. Use variables + env vars
for credentials; mark generated secrets sensitive; never echo a secret key.
Prefer write-only secret arguments (
secret_wo, Terraform ≥ 1.11) so secrets
don't persist in state. See Credentials & secrets.
- Terraform owns managed resources —
mc is for inspection. Changing a
managed resource with mc (or the console) causes drift and confusing
diffs. Use mc to look (and to act on things Terraform doesn't manage),
use Terraform to change what's in state.
The working loop
- Understand intent. Restate the goal in MinIO terms (which buckets,
users, policies, permissions). Resolve ambiguity now, not after
apply.
- Gather context. Read existing
*.tf, check the provider block and
endpoint, and inspect live state where useful:
terraform show / terraform state list — what Terraform manages.
mc admin info <alias>, mc ls <alias>, mc admin user list <alias> —
what actually exists on the server.
- data sources (
minio_s3_buckets, minio_iam_users, minio_server_info)
when you want this inside the config.
- Author / modify HCL. Use exact argument names (see the cheat-sheet and
references/resources.md). Pin the provider. Keep secrets in variables.
- Validate + plan.
terraform fmt, terraform validate, then
terraform plan (with -input=false). Read the diff yourself.
- Present the plan & confirm. Summarize what will be created / changed /
destroyed in plain language, call out anything dangerous (public access,
force_destroy, deletions), and ask for explicit confirmation.
- Apply. Only after confirmation. Then verify (
mc ls, mc stat,
re-run plan to show it's clean).
The safety gate (read vs write)
Classify every command before running it. Read-only → run freely. Mutating →
stop, show the plan/intent, get explicit confirmation.
Terraform — read-only (run freely):
init, validate, fmt, plan, show, output, state list,
state show, providers, providers schema, version, graph.
(init only downloads providers/modules into the working dir — safe.)
Terraform — mutating (require a reviewed plan + explicit confirmation):
apply, destroy, apply -destroy, import, state rm, state mv,
state push, state replace-provider, taint, untaint,
workspace delete. Never pass -auto-approve unless the user approved a plan.
mc — read-only (run freely):
mc ls, mc stat, mc du, mc tree, mc find, mc cat, mc alias list,
mc version, mc admin info, mc admin user list, mc admin group list,
mc admin policy list/info, mc admin config get, mc anonymous get,
mc ilm rule ls, mc replicate ls.
mc — mutating (require explicit confirmation):
mc mb, mc rb, mc rm, mc mv, mc cp/mc mirror (to a target),
mc anonymous set, mc admin user add/remove/enable/disable,
mc admin policy create/attach/detach, mc admin group add/rm,
mc admin config set, mc admin service restart/stop,
mc ilm rule add/rm, mc replicate add/rm.
The single escape hatch is the user explicitly saying "go ahead / apply / skip
the confirmations". Honor it for that operation; don't make it the default.
Provider setup essentials
The provider source is aminueza/minio (not aminueza/terraform-provider-minio).
minio_server is a bare host:port (no scheme); TLS is toggled by minio_ssl,
not by an https:// prefix.
terraform {
required_providers {
minio = {
source = "aminueza/minio"
version = ">= 3.0.0"
}
}
}
provider "minio" {
minio_server = "localhost:9000" # host:port, no scheme; env: MINIO_ENDPOINT
minio_region = "us-east-1" # signing region (matters for S3-compat)
minio_user = var.minio_user # access key; env: MINIO_USER
minio_password = var.minio_password # secret key; env: MINIO_PASSWORD (sensitive)
minio_ssl = false # env: MINIO_ENABLE_HTTPS
}
- Canonical credential args are
minio_user / minio_password. The older
minio_access_key / minio_secret_key are deprecated and conflict if
both pairs are set.
- Common extras:
minio_session_token, minio_insecure, minio_cacert_file,
minio_api_version (v4/v2), s3_compat_mode (for non-MinIO S3 backends
like R2 / B2 / Spaces / Hetzner), plus assume_role and
assume_role_with_web_identity blocks for STS / OIDC.
- Every provider arg has a
MINIO_* env var. Full reference, env-var table,
STS/OIDC/mTLS, and S3-compat caveats: read references/auth-and-config.md.
Credentials & secrets
- Put credentials in variables fed by env vars (
TF_VAR_minio_user, …) or
the provider's own MINIO_* env vars — never hardcode them in committed HCL.
- Resources that generate secrets (
minio_iam_service_account,
minio_accesskey, minio_iam_user with a server-generated secret) expose
secret_key as a sensitive computed attribute that lands in state. Treat
state as secret, and when the provider/Terraform supports it use the
write-only secret_wo argument (Terraform ≥ 1.11) so the value never
persists in state.
- Never print a secret value back to the user in plaintext; reference where it
lives (an output, the service account resource) instead.
High-frequency resource cheat-sheet
Exact argument names for the resources people use most. Get these right —
wrong arg names are the #1 failure. Full schemas for these and every other
resource: references/resources.md. Copy-pasteable end-to-end recipes:
references/recipes.md.
| Resource |
Key arguments (exact) |
Notes / gotchas |
minio_s3_bucket |
bucket (or bucket_prefix), acl, force_destroy, object_locking |
acl defaults to private; force_destroy=true deletes a non-empty bucket. |
minio_s3_bucket_policy |
bucket, policy (JSON) |
Full custom bucket policy. |
minio_s3_bucket_versioning |
bucket, versioning_configuration { status } |
status = "Enabled". |
minio_s3_object |
bucket_name, object_name, content/content_base64/source |
Not bucket/key. |
minio_iam_user |
name, force_destroy, disable_user, secret/secret_wo, tags |
secret_wo (write-only) keeps the secret out of state (TF ≥ 1.11). |
minio_iam_group |
name, force_destroy, disable_group |
|
minio_iam_group_membership |
name, group, users (set) |
Authoritative membership for a group. |
minio_iam_policy |
name (or name_prefix), policy (JSON) |
Managed policy; reference its .id. |
minio_iam_user_policy_attachment |
user_name, policy_name |
Not user/policy. Import id: user-name/policy-name. |
minio_iam_group_policy_attachment |
group_name, policy_name |
Attach a managed policy to a group. |
minio_iam_service_account |
target_user |
Exports sensitive access_key / secret_key. App credentials. |
Easily-confused pairs (which to use when) are spelled out in
references/resources.md → Clarifications: the three group-membership
resources, s3_bucket_lifecycle vs ilm_policy,
s3_bucket_anonymous_access vs s3_bucket_policy, service_account vs
accesskey, inline vs managed policy.
The IAM permissions pattern
Don't hand-write policy JSON when you can build it with the
minio_iam_policy_document data source (AWS-IAM-style) and feed its .json
into a minio_iam_policy, then attach it. This is the canonical, least-error
path for "user/app X can do Y on bucket Z":
data "minio_iam_policy_document" "backups_ro" {
statement {
sid = "ReadBackups"
actions = ["s3:GetObject", "s3:ListBucket"]
resources = ["arn:aws:s3:::backups", "arn:aws:s3:::backups/*"]
}
}
resource "minio_iam_policy" "backups_ro" {
name = "backups-read-only"
policy = data.minio_iam_policy_document.backups_ro.json
}
resource "minio_iam_user" "app" {
name = "backups-reader"
}
resource "minio_iam_user_policy_attachment" "app_backups_ro" {
user_name = minio_iam_user.app.id # note: user_name / policy_name
policy_name = minio_iam_policy.backups_ro.id
}
Note the bucket needs two resource ARNs: the bucket itself (for
ListBucket) and bucket/* (for object actions). For machine credentials an
app actually authenticates with, prefer a minio_iam_service_account (it emits
an access/secret key pair) over embedding the user's own secret.
When a resource isn't covered here
The provider evolves and is bigger than this file. To get an exact, current
schema rather than guessing:
- Authoritative & offline:
terraform providers schema -json | jq '.provider_schemas[].resource_schemas["minio_<name>"]' after terraform init.
- Docs:
https://registry.terraform.io/providers/aminueza/minio/latest/docs/resources/<name> (and .../data-sources/<name>). The URL uses the name without the minio_ prefix.
- Full inventory in this skill:
references/resources.md and
references/data-sources.md.
Reference map
Read the file that matches the task — don't load everything up front.
references/resources.md — every resource grouped by area, one-line purpose
each, exact schemas for the common ones, and the Clarifications section for
confusing pairs. Read when writing/reviewing any resource beyond the
cheat-sheet, or when unsure of an argument name.
references/data-sources.md — every data source (singular/plural pairs,
cluster-info sources). Read when querying existing MinIO state from HCL or
building policy documents.
references/recipes.md — copy-pasteable end-to-end configs (private bucket +
versioning + lifecycle, public/anonymous bucket, read-only user, app service
account, group + policy, replication, OIDC/LDAP attach, importing existing
resources). Read when the user wants a complete working setup.
references/auth-and-config.md — full provider configuration: every argument
- env var,
assume_role, web-identity/OIDC, mTLS, s3_compat_mode,
timeouts/retries, edition. Read when setting up the provider, using STS,
or targeting a non-MinIO S3 backend.
Troubleshooting quick hits
Error: Provider configuration ... AccessDenied / signature errors →
wrong minio_region (S3-compat backends reject mismatched signing regions),
or wrong credentials, or minio_ssl mismatch with the endpoint scheme.
409 / "bucket already exists" on create → the bucket exists out-of-band;
terraform import it instead of recreating. Every resource is importable —
per-resource import syntax is in references/resources.md.
- Plan shows a forced replacement of a user/key you didn't change → likely a
secret/update_secret interaction; see the secret vs secret_wo
clarification in references/resources.md.
- Feature silently does nothing on a non-MinIO backend → with
s3_compat_mode = true the provider skips unsupported features
(notifications, object-lock, CORS, lifecycle) on backends like R2/B2/Spaces.
- Drift / unexpected diffs → something changed the resource via
mc or the
console. Reconcile with terraform apply or terraform import, and stop
managing it both ways.
1---2name: terraform-minio3description: Author, plan, and safely apply Terraform for MinIO and S3-compatible object storage using the aminueza/minio provider. Use this skill whenever the user works with the MinIO Terraform provider, writes or reviews any minio_* resource or data source (minio_s3_bucket, minio_iam_user, minio_iam_policy, minio_iam_service_account, minio_ilm_policy, minio_s3_bucket_replication, minio_s3_bucket_versioning, ...), references the provider source aminueza/minio, or wants to create or change buckets, IAM users, groups, policies, service accounts, lifecycle rules, versioning, replication, encryption, or notifications in MinIO through Terraform. Also use it to translate natural-language requests like "give this app a read-only key for the backups bucket" into correct MinIO HCL, to debug minio provider plan/apply errors, to import existing MinIO resources into Terraform state, and to operate a MinIO endpoint with terraform (and the mc client) using a safe plan-then-confirm workflow.4---56# terraform-minio78An assistant for managing **MinIO** (and S3-compatible) object storage with the9[`aminueza/minio`](https://registry.terraform.io/providers/aminueza/minio/latest)10Terraform provider. It turns natural-language intent ("create a read-only user11for the `backups` bucket", "enable versioning + a 30-day expiry on `logs`") into12**correct HCL**, then drives Terraform through a **safe plan → confirm → apply**13loop — the same safe-by-default philosophy as `kubectl-ai`, adapted to Terraform.1415The provider is large (65 resources, 54 data sources). This file covers the16operating model and the high-frequency surface. For the full inventory and exact17schemas, read the files under `references/` (pointers are given throughout).1819## Operating principles2021These exist because object-storage mistakes are expensive and often22irreversible (a wrong policy exposes a bucket publicly; `force_destroy` deletes23data; a leaked service-account key is a breach). Internalize the *why*, don't24just follow the steps.25261. **Act, don't narrate.** When `terraform` / `mc` are available, *run* the27 read-only and `plan` steps yourself and show the user the results — don't28 just print commands for them to copy. The user wants the outcome, not a29 tutorial. (Stop and hand control back before any mutating step — see the30 safety gate.)312. **Safe by default.** Read-only operations run freely. Anything that mutates32 real infrastructure or state is **gated behind a reviewed plan and explicit33 user confirmation**. See [the safety gate](#the-safety-gate-read-vs-write).343. **Plan before apply, always.** `terraform apply` without first showing the35 user a `terraform plan` is never acceptable. The plan *is* the36 confirmation summary. Use `-auto-approve` **only** after the user has seen a37 plan and said yes.384. **Never invent values.** Don't guess bucket names, regions, ARNs, user names,39 or policy JSON. Inspect what exists first (read the `.tf` files,40 `terraform show`, `mc admin info`, the relevant data sources), then ask the41 user for anything still missing. Assumed defaults on object storage are how42 buckets end up public or data ends up in the wrong region.435. **Secrets never go in HCL or state in plaintext.** Use variables + env vars44 for credentials; mark generated secrets sensitive; never echo a secret key.45 Prefer write-only secret arguments (`secret_wo`, Terraform ≥ 1.11) so secrets46 don't persist in state. See [Credentials & secrets](#credentials--secrets).476. **Terraform owns managed resources — `mc` is for inspection.** Changing a48 managed resource with `mc` (or the console) causes drift and confusing49 diffs. Use `mc` to *look* (and to act on things Terraform doesn't manage),50 use Terraform to *change* what's in state.5152## The working loop53541. **Understand intent.** Restate the goal in MinIO terms (which buckets,55 users, policies, permissions). Resolve ambiguity now, not after `apply`.562. **Gather context.** Read existing `*.tf`, check the provider block and57 endpoint, and inspect live state where useful:58 - `terraform show` / `terraform state list` — what Terraform manages.59 - `mc admin info <alias>`, `mc ls <alias>`, `mc admin user list <alias>` —60 what actually exists on the server.61 - data sources (`minio_s3_buckets`, `minio_iam_users`, `minio_server_info`)62 when you want this inside the config.633. **Author / modify HCL.** Use exact argument names (see the cheat-sheet and64 `references/resources.md`). Pin the provider. Keep secrets in variables.654. **Validate + plan.** `terraform fmt`, `terraform validate`, then66 `terraform plan` (with `-input=false`). Read the diff yourself.675. **Present the plan & confirm.** Summarize what will be created / changed /68 **destroyed** in plain language, call out anything dangerous (public access,69 `force_destroy`, deletions), and ask for explicit confirmation.706. **Apply.** Only after confirmation. Then **verify** (`mc ls`, `mc stat`,71 re-run `plan` to show it's clean).7273## The safety gate (read vs write)7475Classify every command before running it. **Read-only → run freely. Mutating →76stop, show the plan/intent, get explicit confirmation.**7778**Terraform — read-only (run freely):**79`init`, `validate`, `fmt`, `plan`, `show`, `output`, `state list`,80`state show`, `providers`, `providers schema`, `version`, `graph`.81(`init` only downloads providers/modules into the working dir — safe.)8283**Terraform — mutating (require a reviewed plan + explicit confirmation):**84`apply`, `destroy`, `apply -destroy`, `import`, `state rm`, `state mv`,85`state push`, `state replace-provider`, `taint`, `untaint`,86`workspace delete`. Never pass `-auto-approve` unless the user approved a plan.8788**mc — read-only (run freely):**89`mc ls`, `mc stat`, `mc du`, `mc tree`, `mc find`, `mc cat`, `mc alias list`,90`mc version`, `mc admin info`, `mc admin user list`, `mc admin group list`,91`mc admin policy list`/`info`, `mc admin config get`, `mc anonymous get`,92`mc ilm rule ls`, `mc replicate ls`.9394**mc — mutating (require explicit confirmation):**95`mc mb`, `mc rb`, `mc rm`, `mc mv`, `mc cp`/`mc mirror` (to a target),96`mc anonymous set`, `mc admin user add`/`remove`/`enable`/`disable`,97`mc admin policy create`/`attach`/`detach`, `mc admin group add`/`rm`,98`mc admin config set`, `mc admin service restart`/`stop`,99`mc ilm rule add`/`rm`, `mc replicate add`/`rm`.100101The single escape hatch is the user explicitly saying "go ahead / apply / skip102the confirmations". Honor it for that operation; don't make it the default.103104## Provider setup essentials105106The provider **source is `aminueza/minio`** (not `aminueza/terraform-provider-minio`).107`minio_server` is a bare `host:port` (no scheme); TLS is toggled by `minio_ssl`,108not by an `https://` prefix.109110```hcl111terraform {112 required_providers {113 minio = {114 source = "aminueza/minio"115 version = ">= 3.0.0"116 }117 }118}119120provider "minio" {121 minio_server = "localhost:9000" # host:port, no scheme; env: MINIO_ENDPOINT122 minio_region = "us-east-1" # signing region (matters for S3-compat)123 minio_user = var.minio_user # access key; env: MINIO_USER124 minio_password = var.minio_password # secret key; env: MINIO_PASSWORD (sensitive)125 minio_ssl = false # env: MINIO_ENABLE_HTTPS126}127```128129- Canonical credential args are `minio_user` / `minio_password`. The older130 `minio_access_key` / `minio_secret_key` are **deprecated** and *conflict* if131 both pairs are set.132- Common extras: `minio_session_token`, `minio_insecure`, `minio_cacert_file`,133 `minio_api_version` (`v4`/`v2`), `s3_compat_mode` (for non-MinIO S3 backends134 like R2 / B2 / Spaces / Hetzner), plus `assume_role` and135 `assume_role_with_web_identity` blocks for STS / OIDC.136- Every provider arg has a `MINIO_*` env var. **Full reference, env-var table,137 STS/OIDC/mTLS, and S3-compat caveats: read `references/auth-and-config.md`.**138139### Credentials & secrets140141- Put credentials in **variables fed by env vars** (`TF_VAR_minio_user`, …) or142 the provider's own `MINIO_*` env vars — never hardcode them in committed HCL.143- Resources that **generate** secrets (`minio_iam_service_account`,144 `minio_accesskey`, `minio_iam_user` with a server-generated secret) expose145 `secret_key` as a **sensitive** computed attribute that lands in state. Treat146 state as secret, and when the provider/Terraform supports it use the147 **write-only** `secret_wo` argument (Terraform ≥ 1.11) so the value never148 persists in state.149- Never print a secret value back to the user in plaintext; reference where it150 lives (an output, the service account resource) instead.151152## High-frequency resource cheat-sheet153154Exact argument names for the resources people use most. **Get these right** —155wrong arg names are the #1 failure. Full schemas for these and every other156resource: `references/resources.md`. Copy-pasteable end-to-end recipes:157`references/recipes.md`.158159| Resource | Key arguments (exact) | Notes / gotchas |160|---|---|---|161| `minio_s3_bucket` | `bucket` (or `bucket_prefix`), `acl`, `force_destroy`, `object_locking` | `acl` defaults to private; `force_destroy=true` deletes a non-empty bucket. |162| `minio_s3_bucket_policy` | `bucket`, `policy` (JSON) | Full custom bucket policy. |163| `minio_s3_bucket_versioning` | `bucket`, `versioning_configuration { status }` | `status = "Enabled"`. |164| `minio_s3_object` | `bucket_name`, `object_name`, `content`/`content_base64`/`source` | **Not** `bucket`/`key`. |165| `minio_iam_user` | `name`, `force_destroy`, `disable_user`, `secret`/`secret_wo`, `tags` | `secret_wo` (write-only) keeps the secret out of state (TF ≥ 1.11). |166| `minio_iam_group` | `name`, `force_destroy`, `disable_group` | |167| `minio_iam_group_membership` | `name`, `group`, `users` (set) | Authoritative membership for a group. |168| `minio_iam_policy` | `name` (or `name_prefix`), `policy` (JSON) | Managed policy; reference its `.id`. |169| `minio_iam_user_policy_attachment` | `user_name`, `policy_name` | **Not** `user`/`policy`. Import id: `user-name/policy-name`. |170| `minio_iam_group_policy_attachment` | `group_name`, `policy_name` | Attach a managed policy to a group. |171| `minio_iam_service_account` | `target_user` | Exports **sensitive** `access_key` / `secret_key`. App credentials. |172173> Easily-confused pairs (which to use when) are spelled out in174> `references/resources.md` → *Clarifications*: the three group-membership175> resources, `s3_bucket_lifecycle` vs `ilm_policy`,176> `s3_bucket_anonymous_access` vs `s3_bucket_policy`, `service_account` vs177> `accesskey`, inline vs managed policy.178179## The IAM permissions pattern180181Don't hand-write policy JSON when you can build it with the182**`minio_iam_policy_document`** data source (AWS-IAM-style) and feed its `.json`183into a `minio_iam_policy`, then attach it. This is the canonical, least-error184path for "user/app X can do Y on bucket Z":185186```hcl187data "minio_iam_policy_document" "backups_ro" {188 statement {189 sid = "ReadBackups"190 actions = ["s3:GetObject", "s3:ListBucket"]191 resources = ["arn:aws:s3:::backups", "arn:aws:s3:::backups/*"]192 }193}194195resource "minio_iam_policy" "backups_ro" {196 name = "backups-read-only"197 policy = data.minio_iam_policy_document.backups_ro.json198}199200resource "minio_iam_user" "app" {201 name = "backups-reader"202}203204resource "minio_iam_user_policy_attachment" "app_backups_ro" {205 user_name = minio_iam_user.app.id # note: user_name / policy_name206 policy_name = minio_iam_policy.backups_ro.id207}208```209210Note the bucket needs **two** resource ARNs: the bucket itself (for211`ListBucket`) and `bucket/*` (for object actions). For machine credentials an212app actually authenticates with, prefer a `minio_iam_service_account` (it emits213an access/secret key pair) over embedding the user's own secret.214215## When a resource isn't covered here216217The provider evolves and is bigger than this file. To get an exact, current218schema rather than guessing:219220- **Authoritative & offline:** `terraform providers schema -json | jq '.provider_schemas[].resource_schemas["minio_<name>"]'` after `terraform init`.221- **Docs:** `https://registry.terraform.io/providers/aminueza/minio/latest/docs/resources/<name>` (and `.../data-sources/<name>`). The URL uses the name **without** the `minio_` prefix.222- **Full inventory in this skill:** `references/resources.md` and223 `references/data-sources.md`.224225## Reference map226227Read the file that matches the task — don't load everything up front.228229- `references/resources.md` — every resource grouped by area, one-line purpose230 each, exact schemas for the common ones, and the *Clarifications* section for231 confusing pairs. **Read when** writing/reviewing any resource beyond the232 cheat-sheet, or when unsure of an argument name.233- `references/data-sources.md` — every data source (singular/plural pairs,234 cluster-info sources). **Read when** querying existing MinIO state from HCL or235 building policy documents.236- `references/recipes.md` — copy-pasteable end-to-end configs (private bucket +237 versioning + lifecycle, public/anonymous bucket, read-only user, app service238 account, group + policy, replication, OIDC/LDAP attach, **importing existing239 resources**). **Read when** the user wants a complete working setup.240- `references/auth-and-config.md` — full provider configuration: every argument241 + env var, `assume_role`, web-identity/OIDC, mTLS, `s3_compat_mode`,242 timeouts/retries, edition. **Read when** setting up the provider, using STS,243 or targeting a non-MinIO S3 backend.244245## Troubleshooting quick hits246247- **`Error: Provider configuration ... AccessDenied` / signature errors** →248 wrong `minio_region` (S3-compat backends reject mismatched signing regions),249 or wrong credentials, or `minio_ssl` mismatch with the endpoint scheme.250- **`409` / "bucket already exists" on create** → the bucket exists out-of-band;251 `terraform import` it instead of recreating. Every resource is importable —252 per-resource import syntax is in `references/resources.md`.253- **Plan shows a forced replacement of a user/key you didn't change** → likely a254 `secret`/`update_secret` interaction; see the `secret` vs `secret_wo`255 clarification in `references/resources.md`.256- **Feature silently does nothing on a non-MinIO backend** → with257 `s3_compat_mode = true` the provider skips unsupported features258 (notifications, object-lock, CORS, lifecycle) on backends like R2/B2/Spaces.259- **Drift / unexpected diffs** → something changed the resource via `mc` or the260 console. Reconcile with `terraform apply` or `terraform import`, and stop261 managing it both ways.