Skip to main content
Uncloud is designed with security in mind, providing encryption, authentication, and isolation by default. This guide covers the security architecture and best practices for production deployments.

Security Overview

Uncloud’s security is built on several layers:
  1. Network encryption: WireGuard encrypts all cluster communication
  2. SSH authentication: Machine access controlled through SSH keys
  3. TLS certificates: Automatic HTTPS for public-facing services
  4. Container isolation: Docker provides process and namespace isolation
  5. Firewall protection: Automatic iptables rules limit attack surface

WireGuard Encryption

All traffic between machines flows through WireGuard, which provides strong cryptographic protection.

Encryption Primitives

WireGuard uses modern, peer-reviewed cryptography:
  • Data encryption: ChaCha20 stream cipher
  • Authentication: Poly1305 MAC
  • Key exchange: Curve25519 elliptic curve Diffie-Hellman
  • Handshake: Noise protocol framework
  • Hashing: BLAKE2s
This is cryptographically stronger than most VPN protocols (like OpenVPN with default settings) and significantly faster.

What WireGuard Protects

WireGuard encrypts:
  • Container-to-container traffic across machines
  • Machine-to-machine API calls (Uncloud daemon gRPC)
  • State synchronization (Corrosion gossip)
  • Image transfers (Unregistry communication)
Without WireGuard, this traffic would be vulnerable to eavesdropping and tampering.

Key Management

WireGuard keys are generated automatically when machines join the cluster:
  1. Key generation: Each machine generates a random 32-byte private key
  2. Public key derivation: Public key is derived from private key
  3. Key distribution: Public keys are distributed via Corrosion state
  4. Automatic configuration: Keys are configured in WireGuard without manual intervention
Private keys never leave the machine they’re generated on.

Key Storage

Private keys are stored in the machine state file:
# Machine state file (contains private key)
/var/lib/uncloud/machine.json
The machine state file contains the WireGuard private key. Protect this file with appropriate permissions (0600) and never commit it to version control or share it publicly.

Rotating WireGuard Keys

Currently, Uncloud doesn’t support automatic key rotation. The WireGuard private key is generated once when the machine joins the cluster and remains constant. To rotate keys manually:
  1. Remove the machine from the cluster: uc machine rm <name>
  2. Reinitialize or add the machine: uc machine add user@host
  3. This generates a new key pair and updates all peers
Key rotation will cause brief connectivity disruption as peers update their configurations.

SSH Authentication

SSH is the primary authentication mechanism for accessing machines and managing the cluster.

SSH Key Requirements

Uncloud requires SSH key-based authentication. Password authentication is not supported. When initializing a machine:
# Uses default SSH key (~/.ssh/id_ed25519)
uc machine init root@host

# Specify custom SSH key
uc machine init -i ~/.ssh/my-key root@host

Supported Key Types

Uncloud supports all SSH key types:
  • Ed25519 (recommended): Modern, secure, fast
  • RSA 2048+: Widely compatible, requires minimum 2048-bit key
  • ECDSA: Supported but Ed25519 is preferred
Generate a secure SSH key:
# Ed25519 (recommended)
ssh-keygen -t ed25519 -C "[email protected]"

# RSA 4096-bit (widely compatible)
ssh-keygen -t rsa -b 4096 -C "[email protected]"

SSH Key Distribution

When initializing a cluster, Uncloud:
  1. Uses your SSH key to connect to the machine
  2. Installs the Uncloud daemon
  3. Creates a non-root uncloud user (if not root)
  4. Adds your SSH public key to authorized_keys
For subsequent operations, the CLI connects via SSH using your key.

Multi-User Access

To grant cluster access to multiple users:
# On each machine via SSH
ssh user@machine
sudo -u uncloud nano ~/.ssh/authorized_keys
# Add additional public keys
Each user can then use the cluster with their own SSH key:
# User 1
uc --connect user@machine ps

# User 2 with different key
uc --connect user@machine -i ~/.ssh/user2-key ps

SSH Security Best Practices

  1. Use Ed25519 keys: Stronger and faster than RSA
  2. Protect private keys: Use passphrase-protected keys
  3. Use SSH agent: Avoid typing passphrases repeatedly
  4. Disable password auth: Ensure /etc/ssh/sshd_config has PasswordAuthentication no
  5. Limit SSH access: Use firewall rules to restrict SSH to known IPs
  6. Rotate keys periodically: Generate new SSH keys and update authorized_keys

TLS Certificates

Caddy automatically obtains and renews TLS certificates for HTTPS endpoints.

Automatic HTTPS

When you publish a service with a domain:
uc run -p app.example.com:8000/https nginx
Caddy:
  1. Receives the request for app.example.com
  2. Checks if it has a valid certificate
  3. If not, requests one from Let’s Encrypt using ACME HTTP-01 challenge
  4. Stores the certificate locally
  5. Serves HTTPS traffic with the certificate

Certificate Storage

Caddy stores certificates in:
/var/lib/uncloud/caddy/certificates/
Certificates are automatically renewed before expiration (Let’s Encrypt certificates expire after 90 days).

Certificate Challenges

Caddy supports multiple ACME challenge types:
  • HTTP-01 (default): Requires port 80 to be accessible
  • TLS-ALPN-01: Uses port 443
  • DNS-01: Requires DNS provider API access (for wildcards)
For wildcard certificates, use DNS-01 challenge with Caddy configuration.

Custom Certificates

To use your own certificates instead of Let’s Encrypt:
compose.yaml
services:
  web:
    image: nginx
    labels:
      caddy: app.example.com
      caddy.tls: /path/to/cert.pem /path/to/key.pem
Or configure Caddy globally to use custom certificates.

TLS Best Practices

  1. Use strong ciphers: Caddy defaults to secure TLS 1.2+ ciphersuites
  2. Enable HSTS: Configure Caddy to send Strict-Transport-Security headers
  3. Monitor certificate expiration: Set up alerts for certificate renewal failures
  4. Use certificate transparency: Let’s Encrypt certificates are automatically logged
For HTTP-01 challenges to work, port 80 must be accessible from the internet. If you’re behind a firewall or NAT, configure port forwarding or use DNS-01 challenge instead.

Container Isolation

Docker provides multiple layers of isolation between containers and the host system.

Process Isolation

Each container runs in its own set of Linux namespaces:
  • PID namespace: Container processes can’t see host processes
  • Network namespace: Separate network stack per container
  • Mount namespace: Isolated filesystem view
  • UTS namespace: Separate hostname
  • IPC namespace: Isolated inter-process communication
This means a compromised container has limited visibility into the host or other containers.

Resource Limits

Set resource limits to prevent containers from consuming excessive resources:
compose.yaml
services:
  web:
    image: nginx
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
This uses cgroups to enforce CPU and memory limits.

Security Profiles

Docker uses security profiles to restrict container capabilities:
  • AppArmor (Ubuntu/Debian): Confines containers with mandatory access control
  • SELinux (RHEL/CentOS): Enforces security policies
  • Seccomp: Filters system calls available to containers
These are enabled by default in modern Docker installations.

User Namespaces

For enhanced isolation, enable user namespaces to remap container UIDs:
# On the machine via SSH
sudo nano /etc/docker/daemon.json
{
  "userns-remap": "default"
}
sudo systemctl restart docker
This makes root inside the container map to an unprivileged user on the host.

Read-Only Root Filesystem

Run containers with read-only root filesystems when possible:
compose.yaml
services:
  api:
    image: myapp/api
    read_only: true
    tmpfs:
      - /tmp
      - /var/run
This prevents attackers from modifying container filesystems.

Network Security

Firewall Rules

Uncloud automatically configures iptables rules to:
  1. Accept WireGuard traffic: Allow UDP port 51820 from anywhere
  2. Accept Unregistry traffic: Allow TCP port 5000 from cluster machines
  3. Accept cluster traffic: Allow traffic from WireGuard mesh network
  4. Drop other traffic: Default deny for other incoming connections
View the rules:
# On the machine via SSH
sudo iptables -L -v -n

Port Exposure

By default, containers don’t expose ports to the public internet. You must explicitly publish them:
compose.yaml
services:
  # Private: Only accessible within cluster
  database:
    image: postgres:16

  # Public: Exposed via Caddy HTTPS
  web:
    image: nginx
    labels:
      caddy: app.example.com
      caddy.reverse_proxy: "{{upstreams 80}}"

Network Segmentation

For additional security, segment services using Docker networks:
compose.yaml
services:
  web:
    image: nginx
    networks:
      - frontend

  api:
    image: myapp/api
    networks:
      - frontend
      - backend

  database:
    image: postgres:16
    networks:
      - backend

networks:
  frontend:
  backend:
This ensures web can’t directly access database.

Restricting Outbound Traffic

To prevent data exfiltration, restrict outbound connections:
# On the machine via SSH
sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -d <allowed-ip> -j ACCEPT
sudo iptables -A OUTPUT -j DROP
Combine with Docker network segmentation for defense in depth.

Best Practices for Production

1. Minimize Attack Surface

  • Run only necessary services
  • Disable unused features (for example, SSH password auth)
  • Keep software updated (OS, Docker, Uncloud)
  • Remove default accounts and weak credentials

2. Use Strong Authentication

  • Use Ed25519 SSH keys
  • Protect private keys with passphrases
  • Rotate SSH keys periodically
  • Use multi-factor authentication for SSH (for example, Google Authenticator)

3. Enable Logging and Monitoring

# Centralize logs from all machines
sudo journalctl -u uncloud -f
sudo journalctl -u docker -f
  • Monitor for failed authentication attempts
  • Alert on suspicious activity (unusual process, network connections)
  • Retain logs for forensic analysis

4. Secure Container Images

  • Scan images for vulnerabilities (for example, docker scan, Trivy)
  • Use minimal base images (Alpine, distroless)
  • Don’t include secrets in images
  • Regularly update base images
  • Sign images for integrity verification

5. Implement Defense in Depth

  • Network segmentation (Docker networks)
  • Resource limits (CPU, memory)
  • Read-only filesystems where possible
  • Principle of least privilege
  • Regular security audits

6. Protect Sensitive Data

compose.yaml
services:
  api:
    image: myapp/api
    environment:
      - DATABASE_URL=/run/secrets/db_url
    secrets:
      - db_url

secrets:
  db_url:
    file: ./secrets/db_url.txt
  • Use Docker secrets for sensitive configuration
  • Encrypt data at rest (for example, encrypted volumes)
  • Use TLS for all external communication
  • Rotate secrets regularly

7. Plan for Incident Response

  • Document incident response procedures
  • Practice disaster recovery scenarios
  • Maintain backups of critical data
  • Have a process for patching vulnerabilities quickly

8. Network Isolation

For highly sensitive environments:
  • Run Uncloud cluster on a private network
  • Use VPN or bastion hosts for SSH access
  • Implement network policies to restrict traffic
  • Use dedicated security machines as entry points

9. Regular Security Audits

# Check for outdated packages
ssh user@machine
sudo apt update && sudo apt list --upgradable

# Verify firewall rules
sudo iptables -L -v -n

# Review active connections
sudo ss -tuln

# Check running containers
docker ps

# Scan for vulnerabilities
docker scan myapp/image:latest
Perform these checks regularly across all machines.

10. Keep Uncloud Updated

Uncloud receives security updates and bug fixes:
# Update Uncloud CLI
brew upgrade uncloud
# or
curl -fsS https://get.uncloud.run/install.sh | sh

# Update daemon on machines
uc machine upgrade
Stay informed about security advisories through the Uncloud Discord or GitHub releases.

Security Limitations

Uncloud is designed for trusted environments where all cluster administrators are trusted. It’s not designed to provide multi-tenancy or protect against malicious cluster members.

What Uncloud Protects Against

  • External attackers intercepting cluster traffic
  • Unauthorized access to machines (via SSH)
  • Man-in-the-middle attacks on cluster communication
  • Container breakout (via Docker isolation)

What Uncloud Doesn’t Protect Against

  • Malicious cluster administrators with SSH access
  • Compromised SSH keys
  • Vulnerabilities in container applications
  • Social engineering attacks
  • Physical access to machines
For multi-tenant or zero-trust environments, consider additional security layers like network policies, RBAC, or service mesh (Istio, Linkerd).

Compliance Considerations

If you’re subject to compliance requirements (GDPR, HIPAA, PCI-DSS):
  1. Data encryption: WireGuard provides encryption in transit; use encrypted volumes for data at rest
  2. Access control: Implement strict SSH key management and logging
  3. Audit trails: Enable comprehensive logging and retain for required periods
  4. Data residency: Deploy machines in compliant regions
  5. Vulnerability management: Establish patching procedures and scan images regularly
Consult with compliance experts to ensure Uncloud deployments meet your specific requirements.

Reporting Security Issues

If you discover a security vulnerability in Uncloud:
  1. Do not open a public GitHub issue
  2. Email security details to the maintainer (check GitHub profile)
  3. Include steps to reproduce and potential impact
  4. Allow time for a fix before public disclosure
Responsible disclosure helps protect all Uncloud users.

Build docs developers (and LLMs) love