|
| 1 | +--- |
| 2 | +SPDX-License-Identifier: MIT |
| 3 | +path: "/tutorials/hetzner-object-storage-custom-domain" |
| 4 | +slug: "hetzner-object-storage-custom-domain" |
| 5 | +date: "2025-01-17" |
| 6 | +title: "Setup custom domain for S3-compatible object storage via reverse proxy" |
| 7 | +short_description: "This tutorial explains how to setup custom domains for S3-compatible object storage using reverse proxy." |
| 8 | +tags: ["Custom domain", "Reverse proxy", "Object Storage"] |
| 9 | +author: "Ivan Zaitsev" |
| 10 | +author_link: "https://github.com/ivan-zaitsev" |
| 11 | +author_img: "https://avatars.githubusercontent.com/u/15122759" |
| 12 | +author_description: "" |
| 13 | +language: "en" |
| 14 | +available_languages: ["en"] |
| 15 | +header_img: "header-7" |
| 16 | +cta: "cloud" |
| 17 | +--- |
| 18 | + |
| 19 | +## Introduction |
| 20 | + |
| 21 | +This tutorial will guide you to setup a custom domain for S3-compatible object storage using reverse proxy. |
| 22 | +The advantages of a custom domain are to enable seamless integration with existing infrastructure or services under a unified domain. |
| 23 | + |
| 24 | +There are different ways to configure a custom domain, such as using a CNAME record or a reverse proxy. |
| 25 | +This tutorial focuses on configuring a custom domain using a reverse proxy. |
| 26 | + |
| 27 | +**Prerequisites** |
| 28 | + |
| 29 | +* A server (e.g. with [Hetzner Cloud](https://www.hetzner.com/cloud/)) |
| 30 | +* An S3-compatible bucket (e.g. with [Hetzner](https://www.hetzner.com/storage/object-storage/)) |
| 31 | +* A domain you want to use (e.g. `storage.example.com`). |
| 32 | + |
| 33 | +## Step 1 - Create Object Storage Bucket |
| 34 | + |
| 35 | +Create an S3-compatible bucket. |
| 36 | +With Hetzner, see the getting started "[Creating a Bucket](https://docs.hetzner.com/storage/object-storage/getting-started/creating-a-bucket)". |
| 37 | +Make sure it is set to public access permissions. Not much benefit to using a custom domain for private buckets. |
| 38 | + |
| 39 | +Create S3 credentials to access your bucket. |
| 40 | +With Hetzner, see the getting started "[Generating S3 keys](https://docs.hetzner.com/storage/object-storage/getting-started/generating-s3-keys)". |
| 41 | + |
| 42 | +## Step 2 - Create Server |
| 43 | + |
| 44 | +Create a new server. |
| 45 | +With Hetzner, see the getting started "[Creating a Server](https://docs.hetzner.com/cloud/servers/getting-started/creating-a-server)". |
| 46 | +To install Docker and Docker Compose, follow the [official Docker documentation](https://docs.docker.com/engine/install/). |
| 47 | + |
| 48 | +## Step 3 - Deploy Caddy |
| 49 | + |
| 50 | +SSH to your server `ssh root@<server-ip>`. |
| 51 | + |
| 52 | +Create a directory for your Docker Compose files and folders for the persistent storage of the Caddy container: |
| 53 | + |
| 54 | +```bash |
| 55 | +sudo mkdir -p /opt/caddy/data |
| 56 | +``` |
| 57 | + |
| 58 | +### Step 3.1 - Create Docker deployment and configuration files |
| 59 | + |
| 60 | +* Add a Docker compose file |
| 61 | + |
| 62 | + ```bash |
| 63 | + sudo vim /opt/caddy/compose.yaml |
| 64 | + ``` |
| 65 | + Add the following content: |
| 66 | + ```yaml |
| 67 | + services: |
| 68 | + caddy: |
| 69 | + container_name: caddy |
| 70 | + image: caddy:latest |
| 71 | + restart: unless-stopped |
| 72 | + ports: |
| 73 | + - 80:80 |
| 74 | + - 443:443 |
| 75 | + volumes: |
| 76 | + - ./data/Caddyfile:/etc/caddy/Caddyfile |
| 77 | + - ./data/certs:/certs |
| 78 | + - ./data/config:/config |
| 79 | + - ./data/data:/data |
| 80 | + - ./data/sites:/srv |
| 81 | + ``` |
| 82 | +
|
| 83 | +<br> |
| 84 | +
|
| 85 | +* Add a Caddyfile |
| 86 | +
|
| 87 | + ```bash |
| 88 | + sudo vim /opt/caddy/data/Caddyfile |
| 89 | + ``` |
| 90 | + Add the following content: |
| 91 | + |
| 92 | + > Replace `storage.example.com` with your own domain. |
| 93 | + > Replace `fsn1.your-objectstorage.com` with the endpoint of your object storage bucket. If the bucket name comes after the endpoint (e.g. `https://s3-endpoint.example.org/<bucket_name>`) add your endpoint without the bucket name. |
| 94 | + |
| 95 | + ```text |
| 96 | + storage.example.com { |
| 97 | + |
| 98 | + tls { |
| 99 | + issuer acme { |
| 100 | + dir https://acme-v02.api.letsencrypt.org/directory |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + reverse_proxy https://<bucket_name>.fsn1.your-objectstorage.com { |
| 105 | + #reverse_proxy https://s3-endpoint.example.org { |
| 106 | + header_up Host {http.reverse_proxy.upstream.hostport} |
| 107 | + header_up X-Forwarded-Host {host} |
| 108 | + } |
| 109 | + } |
| 110 | + ``` |
| 111 | + |
| 112 | +### Step 3.2 - Start Caddy |
| 113 | + |
| 114 | +```bash |
| 115 | +cd /opt/caddy |
| 116 | +docker compose up -d |
| 117 | +docker ps |
| 118 | +``` |
| 119 | + |
| 120 | +After the Docker container started, you can access your files via `storage.example.com`. |
| 121 | + |
| 122 | +If your bucket name comes after the endpoint, note: |
| 123 | + |
| 124 | +The request URL would be `https://storage.example.com/<bucket_name>/object.txt`. |
| 125 | +It is equivalent to `https://s3-endpoint.example.org/<bucket_name>/object.txt`. |
| 126 | + |
| 127 | +### Step 3.3 - Create Kubernetes deployment and configuration files (Optional) |
| 128 | + |
| 129 | +Assuming you already have configured Kubernetes, [gateway API](https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api). |
| 130 | + |
| 131 | +> Replace `storage.example.com` with your own domain. |
| 132 | +> Replace `fsn1.your-objectstorage.com` with the endpoint of your object storage bucket. If the bucket name comes after the endpoint (e.g. `https://s3-endpoint.example.org/<bucket_name>`) add your endpoint without the bucket name. |
| 133 | +
|
| 134 | +```yaml |
| 135 | +apiVersion: v1 |
| 136 | +kind: Service |
| 137 | +metadata: |
| 138 | + name: caddy-storage |
| 139 | + namespace: caddy |
| 140 | +spec: |
| 141 | + type: ClusterIP |
| 142 | + selector: |
| 143 | + service: caddy-storage |
| 144 | + ports: |
| 145 | + - name: http |
| 146 | + protocol: TCP |
| 147 | + port: 80 |
| 148 | + targetPort: 80 |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +apiVersion: apps/v1 |
| 153 | +kind: Deployment |
| 154 | +metadata: |
| 155 | + name: caddy-storage |
| 156 | + namespace: caddy |
| 157 | +spec: |
| 158 | + replicas: 1 |
| 159 | + revisionHistoryLimit: 3 |
| 160 | + selector: |
| 161 | + matchLabels: |
| 162 | + service: caddy-storage |
| 163 | + template: |
| 164 | + metadata: |
| 165 | + labels: |
| 166 | + service: caddy-storage |
| 167 | + spec: |
| 168 | + containers: |
| 169 | + - image: "caddy:latest" |
| 170 | + name: caddy |
| 171 | + ports: |
| 172 | + - name: http |
| 173 | + protocol: TCP |
| 174 | + containerPort: 80 |
| 175 | + volumeMounts: |
| 176 | + - name: config-volume |
| 177 | + mountPath: /etc/caddy |
| 178 | + volumes: |
| 179 | + - name: config-volume |
| 180 | + configMap: |
| 181 | + name: caddy-storage-config |
| 182 | + |
| 183 | +--- |
| 184 | + |
| 185 | +apiVersion: v1 |
| 186 | +kind: ConfigMap |
| 187 | +metadata: |
| 188 | + name: caddy-storage-config |
| 189 | + namespace: caddy |
| 190 | +data: |
| 191 | + Caddyfile: | |
| 192 | + storage.example.com:80 { |
| 193 | + reverse_proxy https://<bucket_name>.fsn1.your-objectstorage.com { |
| 194 | + #reverse_proxy https://s3-endpoint.example.org { |
| 195 | + header_up Host {http.reverse_proxy.upstream.hostport} |
| 196 | + header_up X-Forwarded-Host {host} |
| 197 | + } |
| 198 | + } |
| 199 | +
|
| 200 | +--- |
| 201 | + |
| 202 | +apiVersion: gateway.networking.k8s.io/v1 |
| 203 | +kind: HTTPRoute |
| 204 | +metadata: |
| 205 | + name: caddy-storage-route |
| 206 | + namespace: caddy |
| 207 | +spec: |
| 208 | + parentRefs: |
| 209 | + - name: kubernetes-gateway |
| 210 | + namespace: istio-gateway |
| 211 | + hostnames: |
| 212 | + - "storage.example.com" |
| 213 | + rules: |
| 214 | + - matches: |
| 215 | + - path: |
| 216 | + type: PathPrefix |
| 217 | + value: / |
| 218 | + backendRefs: |
| 219 | + - name: caddy-storage |
| 220 | + port: 80 |
| 221 | + weight: 100 |
| 222 | +``` |
| 223 | +
|
| 224 | +## Conclusion |
| 225 | +
|
| 226 | +You should now be able to access the contents of your S3-compatible object storage via a custom domain. |
| 227 | +
|
| 228 | +##### License: MIT |
| 229 | +
|
| 230 | +<!-- |
| 231 | +
|
| 232 | +Contributor's Certificate of Origin |
| 233 | +
|
| 234 | +By making a contribution to this project, I certify that: |
| 235 | +
|
| 236 | +(a) The contribution was created in whole or in part by me and I have |
| 237 | + the right to submit it under the license indicated in the file; or |
| 238 | +
|
| 239 | +(b) The contribution is based upon previous work that, to the best of my |
| 240 | + knowledge, is covered under an appropriate license and I have the |
| 241 | + right under that license to submit that work with modifications, |
| 242 | + whether created in whole or in part by me, under the same license |
| 243 | + (unless I am permitted to submit under a different license), as |
| 244 | + indicated in the file; or |
| 245 | +
|
| 246 | +(c) The contribution was provided directly to me by some other person |
| 247 | + who certified (a), (b) or (c) and I have not modified it. |
| 248 | +
|
| 249 | +(d) I understand and agree that this project and the contribution are |
| 250 | + public and that a record of the contribution (including all personal |
| 251 | + information I submit with it, including my sign-off) is maintained |
| 252 | + indefinitely and may be redistributed consistent with this project |
| 253 | + or the license(s) involved. |
| 254 | +
|
| 255 | +Signed-off-by: Ivan Zaitsev, https://github.com/ivan-zaitsev |
| 256 | +
|
| 257 | +--> |
0 commit comments