|
| 1 | +# Publishing services |
| 2 | + |
| 3 | +Publishing service ports makes your services available outside the cluster. This means your services can be accessed |
| 4 | +from the internet or local network, depending on your setup. |
| 5 | + |
| 6 | +You can publish service ports in three ways: |
| 7 | + |
| 8 | +- Using the `-p/--publish` flag with `uc run`. |
| 9 | +- Using the `x-ports` extension in a Compose file with `uc deploy`. |
| 10 | +- Using the `--caddyfile` flag with `uc run` or `x-caddy` extension in a Compose file for custom Caddy configuration. |
| 11 | + |
| 12 | +For example, run a service with container port 8000 exposed as https://app.example.com via Caddy reverse proxy: |
| 13 | + |
| 14 | +```shell |
| 15 | +uc run -p app.example.com:8000/https app:latest |
| 16 | +``` |
| 17 | + |
| 18 | +``` |
| 19 | +[+] Running service app-mwng (replicated mode) 1/1 |
| 20 | + ✔ Container app-mwng-6lub on machine-fnr9 Started |
| 21 | +
|
| 22 | +app-mwng endpoints: |
| 23 | + • https://app.example.com → :8000 |
| 24 | +``` |
| 25 | + |
| 26 | +Create an `A` record in your DNS provider (Cloudflare, Namecheap, etc.) pointing `app.example.com` to the public IP |
| 27 | +address or your machine(s). Once DNS is propagated and Caddy obtains a TLS certificate, you can access your service |
| 28 | +securely over HTTPS. |
| 29 | + |
| 30 | +## Ingress vs host mode |
| 31 | + |
| 32 | +**HTTP/HTTPS** ports are exposed via Caddy using the following format for the `-p/--publish` flag and `x-ports` |
| 33 | +extension: |
| 34 | + |
| 35 | +``` |
| 36 | +[hostname:]container_port[/protocol] |
| 37 | +``` |
| 38 | + |
| 39 | +- `hostname` (optional): The domain name to use for accessing the service. If omitted and a cluster domain is reserved, |
| 40 | + `<service-name>.<cluster-domain>` is used. |
| 41 | +- `container_port`: The port number within the container that's listening for traffic. |
| 42 | +- `protocol` (optional): `http` or `https` (default: `https`) |
| 43 | + |
| 44 | +**TCP/UDP** ports can only be exposed in host mode, which binds the container port directly to the host machine's |
| 45 | +network interface(s). This is useful for non-HTTP services that need direct port access (bypasses Caddy): |
| 46 | + |
| 47 | +``` |
| 48 | +[host_ip:]host_port:container_port[/protocol]@host |
| 49 | +``` |
| 50 | + |
| 51 | +- `host_ip` (optional): The IP address on the host to bind to. If omitted, binds to all interfaces. |
| 52 | +- `host_port`: The port number on the host to bind to. |
| 53 | +- `container_port`: The port number within the container that's listening for traffic. |
| 54 | +- `protocol` (optional): `tcp` or `udp` (default: `tcp`) |
| 55 | + |
| 56 | +| Port value | Description | |
| 57 | +|------------------------------|--------------------------------------------------------------------------------------| |
| 58 | +| `8000/http` | Publish port 8000 as HTTP via Caddy using hostname `<service-name>.<cluster-domain>` | |
| 59 | +| `app.example.com:8080/https` | Publish port 8080 as HTTPS via Caddy using hostname `app.example.com` | |
| 60 | +| `127.0.0.1:5432:5432@host` | Bind TCP port 5432 to host port 5432 on loopback interface only | |
| 61 | +| `53:5353/udp@host` | Bind UDP port 5353 to host port 53 on all network interfaces | |
| 62 | + |
| 63 | +:::warning |
| 64 | + |
| 65 | +Do not publish internal-only services like databases unless absolutely necessary. You only need to publish ports for |
| 66 | +services that should be accessible from outside the cluster. Services within the cluster can communicate with each other |
| 67 | +by their DNS names `service-name` or `service-name.internal` without publishing ports. |
| 68 | + |
| 69 | +::: |
| 70 | + |
| 71 | +## Using Compose |
| 72 | + |
| 73 | +Use the `x-ports` extension in a Compose file to publish service ports: |
| 74 | + |
| 75 | +```yaml title="compose.yaml" |
| 76 | +services: |
| 77 | + app: |
| 78 | + image: app:latest |
| 79 | + x-ports: |
| 80 | + - example.com:8000/https |
| 81 | + - www.example.com:8000/https # The same port can be published with multiple hostnames |
| 82 | + - api.domain.tld:9000/https # Another port can be published with a different hostname |
| 83 | +``` |
| 84 | +
|
| 85 | +## Custom Caddy configuration |
| 86 | +
|
| 87 | +For advanced routing and behavior, use `x-caddy` instead of `x-ports`. It allows you to provide custom Caddy |
| 88 | +configuration for a service in [Caddyfile](https://caddyserver.com/docs/caddyfile) format. |
| 89 | + |
| 90 | +```yaml title="compose.yaml" |
| 91 | +services: |
| 92 | + app: |
| 93 | + image: app:latest |
| 94 | + x-caddy: | |
| 95 | + www.example.com { |
| 96 | + redir https://example.com{uri} permanent |
| 97 | + } |
| 98 | +
|
| 99 | + example.com { |
| 100 | + basic_auth /admin/* { |
| 101 | + admin $2a$14$... # bcrypt hash |
| 102 | + } |
| 103 | +
|
| 104 | + header /static/* Cache-Control max-age=604800 |
| 105 | + reverse_proxy {{upstreams 8000}} { |
| 106 | + import common_proxy |
| 107 | + } |
| 108 | + log |
| 109 | + } |
| 110 | +``` |
| 111 | + |
| 112 | +You can inline the Caddyfile or load it from a file: `x-caddy: ./Caddyfile`. When using a file, the path is relative to |
| 113 | +the Compose file location. See the [Caddy documentation](https://caddyserver.com/docs/caddyfile) for syntax and |
| 114 | +features. |
| 115 | + |
| 116 | +:::info note |
| 117 | + |
| 118 | +You cannot use `x-caddy` with `http` or `https` ports in `x-ports`. `tcp` and `udp` ports in host mode are allowed |
| 119 | +though. |
| 120 | + |
| 121 | +::: |
| 122 | + |
| 123 | +Use it when you need: |
| 124 | + |
| 125 | +- Custom routing rules (different paths, redirects, rewrites, multiple services on one domain). |
| 126 | +- Custom headers, authentication, or caching. |
| 127 | +- Custom load balancing strategies and options. |
| 128 | +- Request and response manipulation. |
| 129 | +- Advanced TLS settings. |
| 130 | +- Other Caddy features and plugins. |
| 131 | + |
| 132 | +See [Deploying or updating Caddy](3-managing-caddy.md#deploying-or-updating-caddy) for details on deploying Caddy with a |
| 133 | +custom global configuration. |
| 134 | + |
| 135 | +### Templates |
| 136 | + |
| 137 | +`x-caddy` configs are processed as [Go templates](https://pkg.go.dev/text/template), allowing you to use dynamic values. |
| 138 | +The following functions and variables are available: |
| 139 | + |
| 140 | +| Template | Description | |
| 141 | +|---------------------------------------|-----------------------------------------------------------------------------------------------| |
| 142 | +| `{{upstreams [service-name] [port]}}` | A space-separated list of healthy container IPs for the current or specified service and port | |
| 143 | +| `{{.Name}}` | The name of the service the config belongs to | |
| 144 | +| `{{.Upstreams}}` | A map of all service names to their healthy container IPs | |
| 145 | + |
| 146 | +The templates are automatically re-rendered and Caddy is reloaded when service containers start/stop or health status |
| 147 | +changes. |
| 148 | + |
| 149 | +**Examples:** |
| 150 | + |
| 151 | +1. Current service upstreams, default port: |
| 152 | + ```caddyfile |
| 153 | + reverse_proxy {{upstreams}} |
| 154 | + ``` |
| 155 | + ↓ |
| 156 | + |
| 157 | + ```caddyfile |
| 158 | + reverse_proxy 10.210.1.3 10.210.2.5 |
| 159 | + ``` |
| 160 | +2. Current service upstreams, port 8000: |
| 161 | + ```caddyfile |
| 162 | + reverse_proxy {{upstreams 8000}} |
| 163 | + ``` |
| 164 | + ↓ |
| 165 | + |
| 166 | + ```caddyfile |
| 167 | + reverse_proxy 10.210.1.3:8000 10.210.2.5:8000 |
| 168 | + ``` |
| 169 | +3. Current service upstreams with `https` scheme: |
| 170 | + ```caddyfile |
| 171 | + reverse_proxy {{- range $ip := index .Upstreams .Name}} https://{{$ip}}{{end}} |
| 172 | + ``` |
| 173 | + ↓ |
| 174 | + |
| 175 | + ```caddyfile |
| 176 | + reverse_proxy https://10.210.1.3 https://10.210.2.5 |
| 177 | + ``` |
| 178 | +4. `api` service upstreams, port 9000: |
| 179 | + ```caddyfile |
| 180 | + handle_path /api/* { |
| 181 | + reverse_proxy {{upstreams "api" 9000}} |
| 182 | + } |
| 183 | + ``` |
| 184 | + ↓ |
| 185 | + |
| 186 | + ```caddyfile |
| 187 | + handle_path /api/* { |
| 188 | + reverse_proxy 10.210.2.2:9000 10.210.1.7:9000 10.210.2.3:9000 |
| 189 | + } |
| 190 | + ``` |
| 191 | + |
| 192 | +### Verifying Caddy config |
| 193 | + |
| 194 | +Use `uc caddy config` to view the complete generated Caddyfile served by the `caddy` service. This is useful for |
| 195 | +debugging and verifying your `x-caddy` configs. |
| 196 | + |
| 197 | +Example output: |
| 198 | + |
| 199 | +```caddyfile |
| 200 | +# This file is autogenerated by Uncloud based on the configuration of running services. |
| 201 | +# Do not edit manually. Any manual changes will be overwritten on the next update. |
| 202 | +
|
| 203 | +# User-defined global config from service 'caddy'. |
| 204 | +*.example.com { |
| 205 | + tls { |
| 206 | + dns cloudflare {env.CLOUDFLARE_API_TOKEN} |
| 207 | + } |
| 208 | + respond "No host matched" 404 |
| 209 | +} |
| 210 | +
|
| 211 | +# Health check endpoint to verify Caddy reachability on this machine. |
| 212 | +http:// { |
| 213 | + handle /.uncloud-verify { |
| 214 | + respond "a369b9388812f9557feef6a0f5b46f2e" 200 |
| 215 | + } |
| 216 | + log |
| 217 | +} |
| 218 | +
|
| 219 | +(common_proxy) { |
| 220 | + # Retry failed requests up to lb_retries times against other available upstreams. |
| 221 | + lb_retries 3 |
| 222 | + # Upstreams are marked unhealthy for fail_duration after a failed request (passive health checking). |
| 223 | + fail_duration 30s |
| 224 | +} |
| 225 | +
|
| 226 | +# Sites generated from service ports. |
| 227 | +
|
| 228 | +https://app.example.com { |
| 229 | + reverse_proxy 10.210.1.3:8000 10.210.2.5:8000 { |
| 230 | + import common_proxy |
| 231 | + } |
| 232 | + log |
| 233 | +} |
| 234 | +
|
| 235 | +https://api.example.com { |
| 236 | + reverse_proxy 10.210.2.2:9000 10.210.1.7:9000 10.210.2.3:9000 { |
| 237 | + import common_proxy |
| 238 | + } |
| 239 | + log |
| 240 | +} |
| 241 | +
|
| 242 | +# User-defined config for service 'web'. |
| 243 | +www.example.com { |
| 244 | + redir https://example.com{uri} permanent |
| 245 | +} |
| 246 | +
|
| 247 | +example.com { |
| 248 | + reverse_proxy 10.210.0.3:8000 { |
| 249 | + import common_proxy |
| 250 | + } |
| 251 | + log |
| 252 | +} |
| 253 | +
|
| 254 | +# Skipped invalid user-defined configs: |
| 255 | +# - service 'duplicate-hostname': validation failed: adapting config using caddyfile adapter: ambiguous site definition: example.com |
| 256 | +# - service 'invalid': validation failed: adapting config using caddyfile adapter: Caddyfile:61: unrecognized directive: invalid_directive |
| 257 | +``` |
| 258 | + |
| 259 | +The generated config combines: |
| 260 | + |
| 261 | +- Global Caddy configuration (`x-caddy` from the `caddy` service). |
| 262 | + See [Deploying or updating Caddy](3-managing-caddy.md#deploying-or-updating-caddy) for details. |
| 263 | +- Auto-generated configs from published service ports (`x-ports`). |
| 264 | +- Custom Caddy configs from services (`x-caddy`). |
| 265 | +- Skipped invalid configs with error messages as comments. |
| 266 | + |
| 267 | +:::warning important |
| 268 | + |
| 269 | +Custom Caddy configs from different services must not conflict (all services must use unique hostnames). |
| 270 | +See [Multiple services on one domain](#multiple-services-on-one-domain) for an example of how to share one hostname |
| 271 | +between multiple services. |
| 272 | + |
| 273 | +Conflicting or invalid configs are detected using [caddy adapt](https://caddyserver.com/docs/command-line#caddy-adapt) |
| 274 | +command and skipped. However, some errors could still break the entire config so Caddy will fail to load it. Check the |
| 275 | +`caddy` service logs to troubleshoot. |
| 276 | + |
| 277 | +::: |
| 278 | + |
| 279 | +### Common use cases |
| 280 | + |
| 281 | +#### Redirects |
| 282 | + |
| 283 | +Publish a service on `example.com` and redirect requests from `www.example.com` to `example.com`: |
| 284 | + |
| 285 | +<Tabs> |
| 286 | +<TabItem value="compose.yaml"> |
| 287 | + |
| 288 | +```yaml |
| 289 | +services: |
| 290 | + app: |
| 291 | + image: app:latest |
| 292 | + x-caddy: ./Caddyfile |
| 293 | +``` |
| 294 | + |
| 295 | +</TabItem> |
| 296 | +<TabItem value="Caddyfile"> |
| 297 | + |
| 298 | +```caddyfile |
| 299 | +www.example.com { |
| 300 | + redir https://example.com{uri} permanent |
| 301 | +} |
| 302 | +
|
| 303 | +example.com { |
| 304 | + reverse_proxy {{upstreams 8000}} { |
| 305 | + import common_proxy |
| 306 | + } |
| 307 | + log |
| 308 | +} |
| 309 | +``` |
| 310 | + |
| 311 | +</TabItem> |
| 312 | +</Tabs> |
| 313 | + |
| 314 | +#### Multiple services on one domain |
| 315 | + |
| 316 | +You can publish multiple services on the same hostname by using different paths for each service. For example, route |
| 317 | +`/` to the web service and `/api` to the API service: |
| 318 | + |
| 319 | +<Tabs> |
| 320 | +<TabItem value="compose.yaml"> |
| 321 | + |
| 322 | +```yaml |
| 323 | +services: |
| 324 | + api: |
| 325 | + image: api:latest |
| 326 | + web: |
| 327 | + image: web:latest |
| 328 | + # Make sure only one service defines a Caddy config for the hostname. |
| 329 | + x-caddy: ./Caddyfile |
| 330 | +``` |
| 331 | + |
| 332 | +</TabItem> |
| 333 | +<TabItem value="Caddyfile"> |
| 334 | + |
| 335 | +```caddyfile |
| 336 | +example.com { |
| 337 | + handle_path /api/* { |
| 338 | + reverse_proxy {{upstreams "api" 9000}} { |
| 339 | + import common_proxy |
| 340 | + } |
| 341 | + } |
| 342 | +
|
| 343 | + reverse_proxy {{upstreams}} { |
| 344 | + import common_proxy |
| 345 | + } |
| 346 | +
|
| 347 | + log |
| 348 | +} |
| 349 | +``` |
| 350 | + |
| 351 | +</TabItem> |
| 352 | +</Tabs> |
0 commit comments