Kamal boots your app on a port on the server; getting a real domain name pointed at it, over TLS,
is a separate, one-time step. There's more than one valid way to do this. Below is the pattern this
kit's own production site uses, worth reading as a worked example even if your own setup differs in
the details.

## The general shape

Whatever specific tools you use, the request path looks like this: your domain resolves to
something in front of your server, that something terminates TLS and forwards plain HTTP to a
reverse proxy on the server, and that reverse proxy forwards to the port Kamal published your app
container on.

## One worked example: Cloudflare Tunnel plus nginx

This is what shiponeshot.com actually runs:

1. **A host nginx vhost** listens on a local port (not the public internet) and reverse-proxies to
   the app container's published port, setting `X-Forwarded-Proto https` since TLS terminates
   further upstream, and forwarding WebSocket upgrade headers for Turbo Streams over Action Cable.
   After adding the vhost, `nginx -t` to check the config, then reload nginx.
2. **A Cloudflare Tunnel** runs on the server and creates an outbound-only connection to
   Cloudflare, with an ingress rule mapping your domain to the local nginx port. Because the
   tunnel is outbound-initiated, the server needs no open inbound ports and no public IP exposure
   at all for web traffic.
3. **DNS**: a proxied CNAME for your domain (and `www`, if you use it) pointing at the tunnel.
   Because Cloudflare proxies the connection, there's no origin certificate to manage and no SSL
   mode to configure on the server; the Cloudflare-to-tunnel hop is already encrypted.

This setup means zero open ports on the server for inbound web traffic, and no certificate renewal
to automate, at the cost of routing all your traffic through Cloudflare's network.

## A simpler alternative

If you'd rather not add a tunnel provider into the mix, a reverse proxy with automatic TLS (for
example, Caddy, or nginx with a Let's Encrypt client) in front of the app container, with your
domain's A record pointed directly at the server's public IP, works fine too, and is a shorter path
to a first deploy. The tradeoff is that you now manage certificate renewal and an open inbound port
yourself, rather than delegating both to a tunnel provider.

## Adding a second app to the same server later

The pattern extends cleanly: a new local port for the new app's container, a new vhost or proxy
rule pointed at that port, a new ingress or DNS entry for the new domain. Nothing about the first
app's setup needs to change.

## Email deliverability, while you're touching DNS

If you're turning on real transactional email, add your email provider's SPF, DKIM, and DMARC
records for your sending domain at the same time you're setting up the rest of your DNS. See
[Email with Resend](/docs/bring-your-own-keys/email-with-resend); skipping this is the single most
common cause of magic-code emails landing in spam.

## Next

With traffic reaching the app, see how the database and file storage behave in production:
[Production Database and Storage](/docs/deployment/production-database-and-storage).
