RPC & Unix Socket Design
Relevant source files
The following files were used as context for generating this wiki page:
Kamal-proxy employs a client-server architecture where a Command Line Interface (CLI) communicates with a long-running proxy server via Remote Procedure Calls (RPC) over a Unix domain socket. This design allows the CLI to perform administrative tasks—such as deploying services, managing rollouts, and inspecting cache or domain status—without requiring the proxy to expose a network-accessible management API.
The communication layer is built using Go's net/rpc package, specifically dialed over a socket file typically named kamal-proxy.sock. This socket serves as the primary control plane, bridging the gap between the user-facing commands in internal/cmd and the core routing logic in internal/server.
Sources: CLAUDE.md:14, internal/cmd/util.go:32-35, internal/server/commands.go:95-101
Architectural Overview
The system is structured into four distinct layers, with the RPC mechanism facilitating interaction between Layer 2 (CLI) and Layer 1 (Server Core). The Unix socket resides at Layer 0, acting as the physical transport for these control signals.
The diagram illustrates the flow of a management command from the CLI through the Unix socket to the server's RPC handler.
Sources: CLAUDE.md:36-40, internal/cmd/util.go:32-35
Server-Side Implementation
The server initializes the RPC environment by registering a CommandHandler and listening on the specified Unix socket path.
Command Registration
The CommandHandler is registered exactly once using rpc.RegisterName with the name "kamal-proxy". This name is a "load-bearing" constant; all client calls must reference this specific string to successfully route calls to the handler's methods.
Sources: CLAUDE.md:20, internal/server/commands.go:88-93
The CommandHandler
The CommandHandler struct serves as the entry point for all RPC methods. It holds references to the Router and Server to execute requested actions.
| Component | Responsibility |
|---|---|
rpcListener | The net.Listener for the Unix socket. |
router | Executes service-level operations (Deploy, Remove, Pause). |
server | Manages global server states, such as draining. |
Sources: internal/server/commands.go:13-17
RPC Method Lifecycle
- Start: The server opens the Unix socket via
net.Listen("unix", socketPath). - Accept: A background goroutine continuously calls
Accept()to receive new connections. - Serve: Each connection is handled in its own goroutine by
rpc.ServeConn(conn).
Sources: internal/server/commands.go:95-120
Client-Side Implementation
The CLI uses a helper function, withRPCClient, to standardize the lifecycle of an RPC connection. This ensures that every command properly dials the socket and closes the connection upon completion.
func withRPCClient(socketPath string, fn func(client *rpc.Client) error) error {
client, err := rpc.Dial("unix", socketPath)
if err != nil {
return err
}
defer client.Close()
return fn(client)
}
Sources: internal/cmd/util.go:32-39
Example: Service Deployment
When a user runs kamal-proxy deploy, the CLI parses flags into a DeployArgs struct and transmits it across the socket.
Sequence of events during a service deployment RPC call.
Sources: internal/cmd/deploy.go:167-170, internal/server/commands.go:128-130
RPC API Definition
The following table summarizes the primary RPC methods available on the "kamal-proxy" service. All methods follow the Go RPC signature: MethodName(Args, *Reply) error.
| Method | Argument Type | Reply Type | Description |
|---|---|---|---|
Deploy | DeployArgs | bool | Deploys or updates a service and its targets. |
Drain | DrainArgs | bool | Initiates server shutdown and request draining. |
Pause | PauseArgs | bool | Pauses traffic to a specific service. |
Remove | RemoveArgs | bool | Removes a service from the router. |
RolloutDeploy | RolloutDeployArgs | bool | Deploys a new version for a canary rollout. |
CacheStats | CacheStatsArgs | CacheStats | Returns metrics about the response cache. |
DomainsStatus | bool | DomainsStatusResponse | Returns status of dynamic TLS domains. |
Sources: internal/server/commands.go:128-204
Security and Data Integrity
The RPC design enforces security boundaries through several mechanisms:
- Local Access Only: By using Unix sockets, management access is restricted to users with filesystem permissions to the socket file (Layer 0 security).
- Client-Side Hashing: For sensitive data like
BasicAuthcredentials, the CLI performs hashing (SHA-256 with salt) before sending the data over the RPC socket. This ensures plaintext passwords never exist in the socket stream or the server's persisted state files. - Struct Isolation: RPC methods use dedicated
ArgsandResponsestructs (e.g.,DeployArgs,DomainsStatusResponse) to strictly define the data contract between CLI and Server.
Sources: CLAUDE.md:40, internal/server/service.go:142-147, internal/cmd/deploy.go:204-213
Conclusion
The RPC and Unix socket design of kamal-proxy provides a robust and secure control plane for the proxy. By leveraging Go's native RPC capabilities over local sockets, the system achieves a clean separation between command-line tooling and high-performance request routing, while maintaining a strict data contract that protects sensitive configuration information.