How-To Guide
Create S3 Buckets with Crossplane¶
Learn how to provision S3 buckets on Hetzner Object Storage using Crossplane for your applications.
Prerequisites¶
Access to the kup6s Kubernetes cluster
kubectl configured with valid kubeconfig
Understanding of Kubernetes manifests
When to Use Crossplane for S3 Buckets¶
Use Crossplane Bucket resources when:
Your application needs dedicated S3 storage
You want infrastructure-as-code bucket management
Buckets should be lifecycle-managed with your application
Available Regions:
fsn1- Falkenstein (production, recommended)nbg1- Nürnberghel1- Helsinki (disaster recovery)
Bucket Naming Convention¶
CRITICAL: Hetzner S3 bucket names are globally unique across all Hetzner customers (like AWS S3). Generic names like “backup”, “logs”, or “artifacts” will fail because they’re already taken by other customers.
Naming Schema: LOCALPART-NAMESPACE-kup6s
LOCALPART: Descriptive purpose (e.g., “gitlab-artifacts”, “uploads”, “backups”)
NAMESPACE: Kubernetes namespace where the application runs
kup6s: Project identifier suffix (mandatory to ensure global uniqueness)
Examples:
Application buckets:
gitlab-artifacts-gitlabbda-kup6s,uploads-myapp-production-kup6sInfrastructure buckets:
backup-etcd-kup6s,logs-loki-kup6s
Rule: Always suffix bucket names with -kup6s to avoid collisions with other Hetzner customers.
Basic Bucket Creation¶
1. Create Bucket Manifest¶
Create a file data-myapp-kup6s.yaml:
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: data-myapp-kup6s # Following naming convention: LOCALPART-NAMESPACE-kup6s
labels:
app: myapp
namespace: production
spec:
deletionPolicy: Delete # Or Orphan to keep bucket when deleting resource
managementPolicies:
- Observe
- Create
- Delete
# Skip Update to avoid tagging operations (not supported by Hetzner S3)
forProvider:
region: fsn1 # Hetzner region code
providerConfigRef:
name: hetzner-s3 # Infrastructure-managed ProviderConfig
2. Apply the Manifest¶
kubectl apply -f data-myapp-kup6s.yaml
3. Verify Bucket Creation¶
# Check bucket status
kubectl get bucket.s3.aws.upbound.io data-myapp-kup6s
# Should show both SYNCED=True and READY=True
# Example output:
# NAME SYNCED READY EXTERNAL-NAME AGE
# data-myapp-kup6s True True data-myapp-kup6s 1m
4. Access Bucket from Applications¶
The bucket will be available at: https://data-myapp-kup6s.fsn1.your-objectstorage.com
Use the shared S3 credentials (stored in hetzner-s3-creds secret in crossplane-system namespace).
Advanced Configuration¶
Deletion Policy Options¶
Delete (Default):
spec:
deletionPolicy: Delete # Bucket deleted when resource is deleted
managementPolicies: [Observe, Create, Delete]
Orphan (Preserve Bucket):
spec:
deletionPolicy: Orphan # Bucket remains when resource is deleted
managementPolicies: [Observe, Create, Delete]
Management Policies¶
Always include managementPolicies to skip Update operations:
spec:
managementPolicies:
- Observe # Monitor bucket state
- Create # Create bucket
- Delete # Delete bucket
# Skip Update/LateInitialize to avoid tagging (not supported by Hetzner)
Why this is required:
Hetzner S3 doesn’t support tagging operations (returns
501 Not Implemented)Without managementPolicies, Crossplane tries to apply tags after creation
This causes
SYNCED=Falsestatus despite bucket being functionalWith managementPolicies, bucket shows both
SYNCED=TrueandREADY=True
Regional Deployment¶
Choose region based on your use case:
Production (Low Latency):
spec:
managementPolicies: [Observe, Create, Delete]
forProvider:
region: fsn1 # Same region as cluster
Disaster Recovery (Geographic Redundancy):
spec:
managementPolicies: [Observe, Create, Delete]
forProvider:
region: hel1 # Different geographic location
Integration with Applications¶
Accessing S3 Credentials¶
Applications need S3 credentials to access buckets. Use the infrastructure-managed credentials:
apiVersion: v1
kind: Pod
metadata:
name: myapp
namespace: production
spec:
containers:
- name: app
image: myapp:latest
env:
- name: S3_ENDPOINT
value: "https://fsn1.your-objectstorage.com"
- name: S3_BUCKET
value: "data-myapp-kup6s"
- name: S3_REGION
value: "fsn1"
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: hetzner-s3-creds
key: access_key_id
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: hetzner-s3-creds
key: secret_access_key
Note: You’ll need to copy the hetzner-s3-creds secret from crossplane-system namespace to your application’s namespace, or configure appropriate RBAC.
Bucket URL Format¶
Path-Style (Recommended for Hetzner):
https://fsn1.your-objectstorage.com/my-app-bucket/object-key
Virtual-Hosted Style (Also Supported):
https://my-app-bucket.fsn1.your-objectstorage.com/object-key
Troubleshooting¶
Bucket Shows SYNCED=False but READY=True¶
Cause: Missing managementPolicies in bucket spec
Solution: Add managementPolicies to skip Update operations:
kubectl patch bucket.s3.aws.upbound.io my-app-bucket \
-p '{"spec":{"managementPolicies":["Observe","Create","Delete"]}}' --type=merge
Explanation:
Without managementPolicies, Crossplane tries to apply tags after bucket creation
Hetzner S3 doesn’t support tagging (returns
501 Not Implemented)Adding managementPolicies skips Update operations (including tagging)
After patching, bucket should show both
SYNCED=TrueandREADY=True
Bucket Creation Fails¶
Check ProviderConfig:
kubectl get providerconfig.aws.upbound.io hetzner-s3 -o yaml
Ensure it has:
skip_region_validation: truehostnameImmutable: trueservices: [s3]Correct endpoint:
https://fsn1.your-objectstorage.com
Check Credentials:
kubectl get secret hetzner-s3-creds -n crossplane-system
Check Provider Pod:
kubectl get pods -n crossplane-system -l pkg.crossplane.io/provider=provider-aws-s3
kubectl logs -n crossplane-system <provider-pod-name>
Bucket Not Visible in Hetzner Console¶
Wait 30-60 seconds after READY=True status. Bucket creation is asynchronous.
Verify with AWS CLI:
export AWS_ACCESS_KEY_ID=<your-key>
export AWS_SECRET_ACCESS_KEY=<your-secret>
aws s3 ls --endpoint-url https://fsn1.your-objectstorage.com
Cleanup¶
Delete Bucket (with data deletion)¶
kubectl delete bucket.s3.aws.upbound.io data-myapp-kup6s
Warning: If deletionPolicy: Delete, this will delete the bucket and all its contents!
Preserve Bucket (orphan)¶
Ensure
deletionPolicy: Orphanis set:kubectl patch bucket.s3.aws.upbound.io data-myapp-kup6s \ -p '{"spec":{"deletionPolicy":"Orphan"}}' --type=merge
Then delete the resource:
kubectl delete bucket.s3.aws.upbound.io data-myapp-kup6s
The bucket remains in Hetzner Object Storage for manual cleanup later.
AI Assistance Prompt¶
When asking an AI to help create S3 buckets with Crossplane, use this prompt:
Prompt for AI:
I need to create an S3 bucket on Hetzner Object Storage using Crossplane in our kup6s Kubernetes cluster.
Context:
We use Upbound provider-aws-s3 configured for Hetzner Object Storage (S3-compatible)
ProviderConfig name:
hetzner-s3Available regions:
fsn1(Falkenstein, production),nbg1(Nürnberg),hel1(Helsinki)Default region:
fsn1IMPORTANT: Must include
managementPolicies: [Observe, Create, Delete]to avoid tagging issuesCRITICAL: Bucket names must follow convention
LOCALPART-NAMESPACE-kup6s(globally unique across all Hetzner customers)Requirements:
Create a Bucket resource manifest for a bucket named
[LOCALPART]-[NAMESPACE]-kup6sUse region
[fsn1/nbg1/hel1]Set appropriate deletionPolicy (
DeleteorOrphan)Include labels for application:
[APP-NAME]Include managementPolicies to skip Update operations
Ensure bucket name follows naming convention with
-kup6ssuffixPlease provide:
Complete Kubernetes manifest YAML
kubectl apply command
How to verify bucket creation
Environment variables needed in my application pod to access this bucket
Reference:
See 50-B-crossplane-provider-s3.yaml.tpl for ProviderConfig
Documentation: How-To: Create S3 Buckets with Crossplane
Replace [BUCKET-NAME], [fsn1/nbg1/hel1], and [APP-NAME] with your specific values.
See Also¶
ProviderConfig Reference - Complete ProviderConfig documentation
Hetzner Object Storage Docs - Official Hetzner S3 documentation
Apply Infrastructure Changes - How to update infrastructure with OpenTofu