Docs · Deployment

Edge and DNS Setup

Putting a reverse proxy or tunnel in front of the app and pointing DNS at it.

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; 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.