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ürnberg

  • hel1 - 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-kup6s

  • Infrastructure 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=False status despite bucket being functional

  • With managementPolicies, bucket shows both SYNCED=True and READY=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=True and READY=True

Bucket Creation Fails

Check ProviderConfig:

kubectl get providerconfig.aws.upbound.io hetzner-s3 -o yaml

Ensure it has:

  • skip_region_validation: true

  • hostnameImmutable: true

  • services: [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)

  1. Ensure deletionPolicy: Orphan is set:

    kubectl patch bucket.s3.aws.upbound.io data-myapp-kup6s \
      -p '{"spec":{"deletionPolicy":"Orphan"}}' --type=merge
    
  2. 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-s3

  • Available regions: fsn1 (Falkenstein, production), nbg1 (Nürnberg), hel1 (Helsinki)

  • Default region: fsn1

  • IMPORTANT: Must include managementPolicies: [Observe, Create, Delete] to avoid tagging issues

  • CRITICAL: Bucket names must follow convention LOCALPART-NAMESPACE-kup6s (globally unique across all Hetzner customers)

Requirements:

  1. Create a Bucket resource manifest for a bucket named [LOCALPART]-[NAMESPACE]-kup6s

  2. Use region [fsn1/nbg1/hel1]

  3. Set appropriate deletionPolicy (Delete or Orphan)

  4. Include labels for application: [APP-NAME]

  5. Include managementPolicies to skip Update operations

  6. Ensure bucket name follows naming convention with -kup6s suffix

Please 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:


Replace [BUCKET-NAME], [fsn1/nbg1/hel1], and [APP-NAME] with your specific values.

See Also