OpenCloud 6.0 Bare Metal Installation on Debian 13 with Incus
A complete guide to installing OpenCloud 6.0 as a bare metal deployment inside a Debian 13 (Trixie) Incus container, compiled from source with libvips support for full image thumbnail generation including WebP, HEIC, and other modern formats.
Why Bare Metal Instead of Docker?
OpenCloud officially promotes Docker Compose for production deployments. Their own documentation includes this disclaimer for bare metal: “Bare metal deployments are not officially supported by OpenCloud. They are great for quick evaluation but are undocumented and have a minimalist feature set.”
I disagree. For a system administrator with Linux experience, bare metal gives you more control, not less. Here is why I chose this path:
Operational transparency. Every process, config file, and log entry is exactly where you expect it on a standard Linux system. No Docker networking abstractions, no volume mount indirection, no opaque entrypoint scripts between you and the application.
Minimal resource footprint. OpenCloud runs at approximately 300 to 400MB of RAM in production with the libvips-enabled binary, which is remarkably low for a full-featured file sync and collaboration platform. For comparison, a Nextcloud deployment with PHP, MySQL, and Redis easily exceeds 1.2GB. Running bare metal inside an Incus system container avoids the additional overhead that a Docker Compose deployment would introduce: the Docker daemon, Traefik reverse proxy, and multiple container runtimes all competing for resources.
Consistent infrastructure. I manage all services inside Incus system containers on Debian 13. Every container follows the same pattern: systemd services, Nginx reverse proxy, acme.sh certificates, automated backups. Adding Docker to the mix breaks that consistency for no real benefit.
Simpler backups. OpenCloud is entirely file based with no database. A Btrfs or ZFS snapshot of the container captures everything: binary, config, data, metadata. No database dump coordination needed.
Build customization. By compiling from source, I can enable features like libvips for extended image format support that the pre-built binary lacks.
Architecture Overview
The deployment runs on a VPS with Debian 13 and Btrfs (or ZFS), using Incus for container management. The architecture:
- opencloud container (10.10.10.55): OpenCloud 6.0 binary, data, config
- nginx-proxy container (10.10.10.100): Nginx reverse proxy with TLS termination via acme.sh
- opencloud-dev container: dedicated build environment (not running in production)
All containers are self-contained units that can be individually snapshotted, backed up, and restored.
Prerequisites
- A Debian 13 (Trixie) Incus container with a static IP
- Nginx reverse proxy with TLS certificate for your domain
- DNS A/AAAA record pointing to your server
Throughout this guide, set these variables to match your environment:
export OC_DOMAIN="cloud.yourdomain.com"
export OC_PROXY_IP="10.10.10.100" # IP of your Nginx reverse proxy
export OC_CONTAINER_IP="10.10.10.55" # IP of the OpenCloud containerPart 1: Basic Installation with Pre-built Binary
This section gets OpenCloud running quickly. Part 2 covers compiling from source with libvips.
Create the System User and Directories
useradd --system --no-create-home --shell /usr/sbin/nologin opencloud
mkdir -p /opt/opencloud
mkdir -p /var/lib/opencloud/{config,data}
chown -R opencloud:opencloud /var/lib/opencloud
chmod 700 /var/lib/opencloud
I use /var/lib/opencloud for data and config following Linux FHS conventions, and /opt/opencloud for the binary.
Download the Binary
cd /opt/opencloud
wget https://github.com/opencloud-eu/opencloud/releases/download/v6.0.0/opencloud-6.0.0-linux-amd64 -O opencloud
chmod +x opencloud
Create the Configuration
cat > /var/lib/opencloud/config/config.env << EOF
OC_CONFIG_DIR=/var/lib/opencloud/config/
OC_BASE_DATA_PATH=/var/lib/opencloud/data
OC_URL=https://${OC_DOMAIN}
PROXY_LOG_LEVEL=info
OC_INSECURE=true
EOFImportant: The OC_URL must use https:// with your public domain name. OpenCloud’s internal identity provider (IdP) requires HTTPS; it will refuse to start with an HTTP URL. The OC_INSECURE=true flag tells OpenCloud to accept its own self-signed certificate for internal service communication, since TLS termination happens at the Nginx proxy level.
Initialize OpenCloud
sudo -u opencloud env $(cat /var/lib/opencloud/config/config.env | xargs) /opt/opencloud/opencloud init --insecure trueWhen prompted about disabling certificate checking, answer yes. Note down the admin password that is displayed.
You can also set the admin password explicitly during init:
sudo -u opencloud env $(cat /var/lib/opencloud/config/config.env | xargs) /opt/opencloud/opencloud init --insecure true --admin-password YourChosenPasswordIf you forget the password later, it is stored in the generated config file and can be retrieved:
grep "admin_password" /var/lib/opencloud/config/opencloud.yamlSet the Timezone
This is easy to overlook but critical. Debian 13 containers default to UTC. If your server and clients are in a different timezone, OIDC token validation can fail intermittently because of clock skew between token issuance and validation.
timedatectl set-timezone Europe/BrusselsReplace with your actual timezone.
Create the Systemd Service
cat > /etc/systemd/system/opencloud.service << 'EOF'
[Unit]
Description=OpenCloud Service
After=network.target
[Service]
Type=simple
User=opencloud
Group=opencloud
WorkingDirectory=/opt/opencloud
EnvironmentFile=/var/lib/opencloud/config/config.env
ExecStart=/opt/opencloud/opencloud server
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOFStart the Service
systemctl daemon-reload
systemctl enable --now opencloud.service
systemctl status opencloud.serviceSolving the OIDC Loopback Problem
This is the most important gotcha in a reverse proxy setup and will save you hours of debugging.
After login, OpenCloud’s proxy service needs to verify the OIDC access token by connecting to its own public URL (https://cloud.yourdomain.com/.well-known/openid-configuration). Inside the container, this URL resolves to an external IP, and the request either times out or fails because the container cannot reach itself through the public network path.
The solution is to add a hosts entry inside the container pointing the domain to your Nginx proxy’s internal IP:
echo "${OC_PROXY_IP} ${OC_DOMAIN}" >> /etc/hostsReplace 10.0.10.100 with the IP of your reverse proxy container (already set in OC_PROXY_IP). This way, the OIDC validation request goes to the Nginx proxy, which routes it back to OpenCloud. The token issuer URL matches, and authentication completes successfully.
Note: This is the same class of problem you encounter when running WordPress behind a reverse proxy (redirect loops because the application cannot reach its own public URL). If you have dealt with that before, the pattern is familiar.
Make sure this /etc/hosts entry survives container restarts. Depending on your Incus network configuration, DHCP might overwrite it.
Nginx Reverse Proxy Configuration
On your Nginx proxy container, set the same OC_DOMAIN and OC_CONTAINER_IP variables:
export OC_DOMAIN="cloud.yourdomain.com"
export OC_CONTAINER_IP="10.10.10.55"Create the config file using these variables:
cat > /etc/nginx/sites-available/${OC_DOMAIN} << EOF
server {
listen 80;
listen [::]:80;
server_name ${OC_DOMAIN};
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ${OC_DOMAIN};
ssl_certificate /path/to/acme/${OC_DOMAIN}/fullchain.cer;
ssl_certificate_key /path/to/acme/${OC_DOMAIN}/${OC_DOMAIN}.key;
client_max_body_size 0;
location / {
proxy_pass https://${OC_CONTAINER_IP}:9200;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_ssl_verify off;
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
keepalive_timeout 3600s;
keepalive_requests 100000;
proxy_next_upstream off;
}
}
EOFKey points:
proxy_passuseshttps://because OpenCloud always listens on HTTPS with a self-signed certificate, even when TLS termination happens externally.proxy_ssl_verify offtells Nginx to accept that self-signed certificate.client_max_body_size 0removes upload size limits.- IPv6 listeners are included; essential if your clients connect over IPv6.
- Nginx variables (
$server_name,$host, etc.) are escaped with\$in the heredoc so they are written literally into the config file.
Enable and test:
ln -s /etc/nginx/sites-available/${OC_DOMAIN} /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginxOptional Configuration
Once OpenCloud is running, you can enhance the user experience by adding these environment variables to /var/lib/opencloud/config/config.env. Restart the service after making changes.
Language and User Defaults
# Set the default language for the web UI
OC_DEFAULT_LANGUAGE=en
# Prevent demo users from being created
IDM_CREATE_DEMO_USERS=falseEmail Notifications
Without email notifications, users will not be notified when someone shares a file with them or mentions them. If you run your own mail server, this is straightforward:
NOTIFICATIONS_SMTP_HOST=mail.yourdomain.com
NOTIFICATIONS_SMTP_PORT=587
NOTIFICATIONS_SMTP_SENDER=cloud@yourdomain.com
NOTIFICATIONS_SMTP_AUTHENTICATION=login
NOTIFICATIONS_SMTP_USERNAME=cloud@yourdomain.com
NOTIFICATIONS_SMTP_PASSWORD=yourpassword
NOTIFICATIONS_SMTP_INSECURE=falseThumbnail Quality
Increase the available thumbnail resolutions for better preview quality in the web UI:
THUMBNAILS_RESOLUTIONS=16x16,32x32,64x64,128x128,256x256,512x512,1024x1024
THUMBNAILS_MAX_CONCURRENT_REQUESTS=10Storage Quotas
Set a default storage quota per user (in bytes). This is essential for managed hosting:
GRAPH_SPACES_DEFAULT_QUOTA=10737418240 # 10GB per userPassword Security
Enforce a banned password list to prevent weak passwords:
IDM_BANNED_PASSWORD_LIST=/var/lib/opencloud/config/banned-password-list.txtYou can generate a banned password list from common password dictionaries or use a curated list.
Public Sharing Policy
Control whether public share links require a password:
OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD=truePart 2: Compiling from Source with Libvips
The pre-built binary only generates thumbnails for PNG, JPG, GIF, TIFF, BMP, and TXT files. Modern image formats like WebP, HEIC, and AVIF are not supported. To enable them, you need to compile OpenCloud with libvips support.
The ENABLE_VIPS build flag is documented in OpenCloud’s developer documentation under the thumbnails service: docs.opencloud.eu/docs/dev/server/services/thumbnails/information in the “Using libvips for Thumbnail Generation” section. It is not mentioned in the admin guide, which is why it is easy to miss.
Important: Do this in a separate build container, never on your production instance. A failed build can overwrite your running binary and leave you with a broken service.
Set Up the Build Container
incus launch images:debian/13 opencloud-dev
incus shell opencloud-devInstall the toolchain and all image format libraries:
apt update && apt upgrade -y
apt install git wget curl npm pkg-config \
libvips-dev libwebp-dev libheif-dev librsvg2-dev \
libjpeg-dev libpng-dev libtiff-dev libgif-dev libexif-dev -yInstall Go 1.25 (Debian’s packaged version is too old):
wget https://go.dev/dl/go1.25.0.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz
export PATH=/usr/local/go/bin:$PATHInstall corepack and pnpm:
npm install -g corepack
corepack enable pnpmClone and Build
mkdir -p /opt/build && cd /opt/build
git clone https://github.com/opencloud-eu/opencloud.git
cd opencloud
git checkout v6.0.0Run the generate step first and wait for it to complete without errors:
make clean generateVerify the frontend assets were built:
ls services/idp/assets/identifier/index.htmlOnly if that file exists, proceed with the build:
make -C opencloud build ENABLE_VIPS=1Verify the result:
file opencloud/bin/opencloud
ldd opencloud/bin/opencloud | grep vipsYou should see libvips.so in the linked libraries. The binary is dynamically linked, which is expected when libvips is enabled.
Deploy to Production
From the Incus host, copy the binary:
incus file pull opencloud-dev/opt/build/opencloud/opencloud/bin/opencloud /tmp/opencloud-vipsStop the production service, push the new binary, install the runtime library:
incus exec opencloud -- systemctl stop opencloud.service
incus file push /tmp/opencloud-vips opencloud/opt/opencloud/opencloud --mode=0755
incus exec opencloud -- apt install libvips -y
incus exec opencloud -- systemctl start opencloud.serviceThe production container only needs libvips (the runtime library), not the -dev packages or the build toolchain. It stays lean.
Verify Thumbnail Support
Upload a WebP image through the web UI. If the thumbnail renders instead of showing a generic file icon, libvips is working.
The Upgrade Workflow
This build process gives you a repeatable upgrade path:
- In the build container:
git fetch --tags && git checkout v<new-version> make clean generate- Verify generate completed:
ls services/idp/assets/identifier/index.html make -C opencloud build ENABLE_VIPS=1- Snapshot the production container (safety net)
- Stop service, push new binary, restart
The same binary can be deployed to any Linux amd64 container that has libvips installed. Build once, deploy everywhere.
What You Get
After completing this guide, you have:
- OpenCloud 6.0 running bare metal on Debian 13 in an Incus container
- Single Go binary with no database, no PHP, no Redis, no Docker
- ~300 to 400MB RAM footprint (versus 1.2GB+ for a comparable Nextcloud deployment, or 2GB+ for ERPNext)
- Full image thumbnail support including WebP, HEIC, AVIF via libvips
- File-based storage with PosixFS, using extended attributes for metadata
- Proper TLS via Nginx reverse proxy with acme.sh certificates
- Desktop sync working via the OpenCloud Windows/Mac/Linux client
- Snapshot-based backups of the entire container; no database dumps needed
Known Considerations
Token refresh errors in logs. You will see periodic token is expired errors in the journal, roughly every 5 minutes. This is the normal OIDC token refresh cycle; the desktop client’s access token expires, the first request fails with a 401, the client refreshes the token, and the retry succeeds. It looks alarming in the logs but is expected behavior.
OpenCloud must run on the root of a domain. You cannot use a subpath like example.com/opencloud. Use a subdomain like cloud.example.com instead.
The pre-built binary has limited thumbnail support. Without libvips, only PNG, JPG, GIF, TIFF, BMP, and TXT get thumbnails. Modern formats like WebP require compiling from source.
Bare metal is not officially supported. You are responsible for integration with companion services like Collabora (document editing), Apache Tika (full-text search), ClamAV (antivirus), and Radicale (CalDAV/CardDAV). All of these can run natively in their own Incus containers following the same pattern.
Security
OpenCloud has no built-in brute force protection or rate limiting on the login endpoint. Security is handled at the infrastructure level. If you run CrowdSec or fail2ban on your reverse proxy, OpenCloud’s login endpoints are already protected. The PROXY_LOG_LEVEL=info setting in the config produces log entries for failed authentication attempts that can be parsed by either tool.
OpenCloud provides server-side encryption at rest. Files are encrypted automatically when stored on the server, with the key residing on the server. This protects against physical access to drives or backups but does not prevent admin-level access. For zero-knowledge end-to-end encryption where even the server administrator cannot read file content, consider running CryptPad alongside OpenCloud for sensitive collaborative documents.
Next Steps
This guide covers the core OpenCloud server for file storage and sync. The following companion services extend its functionality and will be covered in separate articles:
- Collabora Online for document editing in the browser via WOPI protocol
- Apache Tika for full-text search across PDFs, Office documents, and images
- ClamAV via ICAP for antivirus scanning of uploaded files
- Radicale for CalDAV/CardDAV calendar and contact sync
- CrowdSec integration for brute force protection at the Nginx proxy level
- S3 storage backend for scalable object storage independent of the VPS disk
- Kopia backups to S3-compatible storage with COMPLIANCE mode retention
- Custom branding via theme.json for white-label client deployments
Each companion service runs in its own Incus container following the same bare metal pattern.
This guide was developed and tested on a VPS running Debian 13 with Incus and Btrfs. OpenCloud version 6.0.0, compiled from source with libvips support, deployed behind an Nginx reverse proxy with acme.sh ECC certificates.
