CottonBeginner installation guide

From no server experience to your own Cotton Cloud.

Learn what a server is, compare a home server, NAS, and VPS, understand why Docker and a reverse proxy exist, then follow one complete Ubuntu VPS path with persistent PostgreSQL data and automatic HTTPS.

Ubuntu VPSDocker ComposePostgreSQLCaddy HTTPSPersistent storageFirst server
Start at zero

First understand the pieces. Then install them.

This guide assumes you have never managed a server. It explains the words first, shows how the pieces connect, and then follows one concrete path: an Ubuntu 24.04 VPS, Docker Compose, and automatic HTTPS through Caddy.

Choose a machine

A server is just a computer that stays available.

It does not have to be a rack in a data center. Cotton can live on a computer at home or on a rented virtual machine. The important differences are where the files live, how the machine reaches the internet, and who maintains it.

Your computer

Best for
A short local test before choosing a permanent home.
Main advantage
No new hardware or monthly bill.
What gets harder
Cotton stops when the computer sleeps or leaves the network.

Home server or mini PC

Best for
A personal or family cloud with files kept at home.
Main advantage
You control the disks and local network speed.
What gets harder
Remote access, power, disk health, and backups are your job.

NAS with Docker

Best for
A home archive when the NAS already runs containers reliably.
Main advantage
Storage and disk management are already in one box.
What gets harder
Docker support and folder permissions vary by NAS vendor.

Ubuntu VPS

Best for
The simplest first public install with a domain.
Main advantage
Always on, reachable from the internet, and easy to replace.
What gets harder
Monthly cost and enough provider disk space for your files.
The whole route

What happens when you open your cloud.

The browser never talks to the database or file directory directly. DNS finds the server, Caddy accepts the encrypted web connection, and Cotton decides what data to read or write.

Your browser or app
encrypted HTTPS
Domain and DNS
points to the IP
Your serverEverything inside this frame runs on your server
Caddy: HTTPS entrance
forwards the request
Cotton: the application
reads and writes
PostgreSQL: users and file map
File storage: encrypted chunks
Caddy is the public door. Cotton is the product. PostgreSQL and /data/cotton are the state you must preserve.
What survives

Containers are replaceable. Your data is not.

Updating Docker normally replaces a container. That is safe only because the real state lives outside those replaceable containers.

Safe to recreate

  • Caddy container
  • Cotton container
  • PostgreSQL container

Back up and keep

  • postgres_data volume
  • /data/cotton
  • Master key
  • Caddy certificate data
A database backup without /data/cotton cannot restore the file bytes. Encrypted files without the master key may be unrecoverable.
Why Docker

Docker makes the server repeatable.

Without Docker you would install the correct .NET runtime, PostgreSQL tools, media utilities, users, permissions, and upgrades by hand. Docker packages the application; Compose records how all services start together.

Image

A packaged application template. bvdcode/cotton contains Cotton and the runtime tools it needs.

Container

A running instance of an image. It can be stopped or replaced without becoming your data store.

Compose

One YAML file that starts PostgreSQL, Cotton, and Caddy with the same network and settings.

Volume or mounted folder

Storage outside a replaceable container. This is where the database, file chunks, and certificates persist.

HTTPS

Why Caddy or Traefik sits in front of Cotton.

Cotton listens inside Docker on port 8080. A reverse proxy owns public ports 80 and 443, obtains the TLS certificate, and forwards each HTTPS request to Cotton. What people often call SSL is now TLS; HTTPS is ordinary web traffic protected by TLS. Users see only the normal https:// address.

Recommended here

Caddy

The shortest first install: one domain in a Caddyfile and certificates are requested and renewed automatically.

For an existing stack

Traefik

Useful when one Docker host already routes many services through labels and shared middleware. It adds concepts a first Cotton install does not need.

The path used below

One concrete route instead of a maze of options.

The commands below target Ubuntu Server 24.04 LTS on a VPS. This is not the only valid deployment, but it is a clear baseline a first-time operator can reproduce.

Ubuntu 24.04 VPS

A fresh server and a user that can run sudo. Size the disk for the files you plan to store.

One domain name

Use a subdomain such as cloud.example.com. You will point it at the server IP before starting Caddy.

SSH access

The provider gives you a server IP plus a password or SSH key. SSH is the remote terminal used for the commands below.

Open ports 22, 80, and 443

22 is SSH administration; 80 and 443 let Caddy issue certificates and serve the website. Do not expose PostgreSQL port 5432.

Before the terminal

Two provider tasks happen outside the server.

Create the VPS first. Then open the DNS panel at the company where your domain is managed. The terminal commands cannot do these two account-specific steps for you.

1. Create the VPS

Choose Ubuntu 24.04 LTS, add your SSH key or receive a temporary password, and copy the public IPv4 address. Keep the server region and disk size appropriate for your users and files.

2. Point the domain at it

Create an A record such as cloud.example.com with the VPS IPv4 address. DNS may take time to update. Caddy cannot issue HTTPS until the name resolves to this server.

3. Connect over SSH

Open Terminal on macOS/Linux or Windows Terminal on Windows and run ssh <user>@<server-ip>. Accept the host fingerprint only when the IP matches the server you just created.

Expected result

You see a prompt on the remote Ubuntu server. From this point every command below runs there, not on your personal computer.

docker-compose.yml

One file describes the complete public stack.

PostgreSQL keeps its database in the named postgres_data volume. Cotton keeps encrypted file chunks and its key sentinel under /data/cotton. Caddy is the only public service and stores its certificates in durable volumes.

docker-compose.yml
name: cotton

services:
  postgres:
    image: postgres:18
    restart: unless-stopped
    environment:
      POSTGRES_DB: cotton
      POSTGRES_USER: cotton
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
    volumes:
      - postgres_data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U cotton -d cotton"]
      interval: 5s
      timeout: 5s
      retries: 20

  cotton:
    image: bvdcode/cotton:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    expose:
      - "8080"
    volumes:
      - /data/cotton:/app/files
    environment:
      COTTON_PG_HOST: postgres
      COTTON_PG_PORT: "5432"
      COTTON_PG_DATABASE: cotton
      COTTON_PG_USERNAME: cotton
      COTTON_PG_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
    security_opt:
      - no-new-privileges:true

  caddy:
    image: caddy:2
    restart: unless-stopped
    depends_on:
      - cotton
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    environment:
      COTTON_DOMAIN: ${COTTON_DOMAIN:?Set COTTON_DOMAIN in .env}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

volumes:
  postgres_data:
  caddy_data:
  caddy_config:
Caddyfile

One line connects HTTPS to Cotton.

Caddy reads the domain from .env, requests a trusted TLS certificate, renews it automatically, and forwards traffic to the Cotton container on the private Docker network.

Caddyfile
{$COTTON_DOMAIN} {
  reverse_proxy cotton:8080
}
The .env file

Only two values belong outside Compose.

The setup command generates the database password straight into this protected file. You only replace the example domain. The recommended browser-unlock path deliberately keeps the Cotton master key out of .env.

.env
POSTGRES_PASSWORD=<generated automatically>
COTTON_DOMAIN=cloud.example.com
01

1. Install Docker

These commands install Ubuntu's Docker Engine and Compose package, start Docker now, and enable it after reboot.

The final two commands print Docker and Compose version numbers. If either says command not found, stop here and fix the installation before continuing.
Ubuntu terminal
sudo apt update
sudo apt install -y docker.io docker-compose-v2 curl nano openssl ufw
sudo systemctl enable --now docker
sudo docker --version
sudo docker compose version
02

2. Create the durable folders and configuration

This creates /opt/cotton for configuration and /data/cotton for file data, downloads the two files shown above, writes a generated database password directly to .env, and opens the domain for editing.

In nano, replace cloud.example.com with your real domain, press Ctrl+O, Enter, then Ctrl+X. Do not add https:// and do not put spaces around =.
Ubuntu terminal
sudo install -d -m 0750 /opt/cotton /data/cotton
cd /opt/cotton
sudo curl -fsSLo compose.yml https://cottoncloud.dev/install/docker-compose.yml
sudo curl -fsSLo Caddyfile https://cottoncloud.dev/install/Caddyfile
DB_PASSWORD="$(openssl rand -hex 24)"
printf 'POSTGRES_PASSWORD=%s\nCOTTON_DOMAIN=cloud.example.com\n' "$DB_PASSWORD" | sudo tee .env >/dev/null
sudo chmod 600 .env
sudo nano .env
03

3. Open only the required web ports

Keep SSH open before enabling the firewall so you do not lock yourself out. If the VPS provider has a separate cloud firewall, allow TCP 80 and 443 there too.

ufw status shows OpenSSH, 80/tcp, and 443/tcp allowed. PostgreSQL 5432 and Cotton 8080 remain private.
Ubuntu terminal
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
04

4. Validate and start everything

Compose first checks the file, then downloads the images, starts the three services, and prints their state.

postgres becomes healthy; cotton and caddy are running. The first image download can take several minutes. Caddy obtains HTTPS after DNS reaches this server.
Ubuntu terminal
cd /opt/cotton
sudo docker compose config --quiet
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
05

5. Read the one-time bootstrap token

A new Cotton instance starts locked because the master key is intentionally not stored in Docker. The server log prints a short-lived token for the first unlock.

Copy only the bootstrap token. If it expired, restart Cotton with sudo docker compose restart cotton and read the new token.
Ubuntu terminal
sudo docker compose logs cotton | grep -i "bootstrap token"
First run

Finish in the browser without guessing the order.

Open https://cloud.example.com/unlock with your real domain. The unlock page appears before the normal application because Cotton cannot read encrypted state without its master key.

1. Generate and save the master key

Use Generate on the unlock page, copy the 32-character key into a password manager or offline recovery record, enter the bootstrap token, and unlock. Losing the key can make encrypted data unrecoverable.

2. Create the first administrator

After startup, the login form explains that no users exist. The email and password you submit create the first administrator. Use a unique password.

3. Complete the setup wizard

For this basic path choose local file storage. Answer the usage, privacy, email, and timezone questions; the UI explains each choice and most can be changed later.

4. Confirm the public address

Set the public base URL to the same https:// domain people will open. This keeps links, passkeys, password resets, and OIDC callbacks on the correct origin.

5. Trust only the immediate proxy

In the trusted-proxy setting, use Cotton's observed-proxy check and save the suggested Docker bridge boundary. Do not trust every address on the internet.

After install

Do these before you trust it.

The login page loading is not the finish line.

Turn on 2FA

Enable passkeys or TOTP for the admin account before the instance is reachable from outside.

Run the security checkup

The admin checkup flags public signup, missing 2FA, writable rootfs, Docker socket exposure, and more.

Test persistence

Restart the stack, unlock Cotton again, and verify the administrator and a test upload still exist before adding important files.

Know your update path

Confirm how you pull a new image and restart without losing the database or chunk storage.

Operate it

The two command sequences you will need later.

An install is trustworthy only when you know how to update it and how to take recovery data off the server. A backup stored only on the same VPS is not a backup against losing that VPS.

Update the containers

Read Cotton release notes first. These commands pull current images and recreate services while keeping the named volumes and /data/cotton.

Ubuntu terminal
cd /opt/cotton
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps

Create a PostgreSQL dump

This writes a dated database dump under /var/backups/cotton. Copy that dump, /data/cotton, the master key, and /opt/cotton to separate storage as one recovery set.

Ubuntu terminal
sudo install -d -m 0700 /var/backups/cotton
sudo sh -c 'docker compose -f /opt/cotton/compose.yml exec -T postgres pg_dump -U cotton -d cotton -Fc > /var/backups/cotton/postgres-$(date +%F).dump'
If it does not open

Check one layer at a time.

Do not delete volumes and do not regenerate the master key to make an error disappear. Start with the first failing layer in the path shown above.

The domain does not resolve

Check that the A record contains the VPS public IPv4 address. Until DNS resolves correctly, Caddy cannot obtain a public certificate.

The browser shows a connection error

Check provider firewall rules, then sudo ufw status, then sudo docker compose ps. Ports 80 and 443 must reach Caddy.

Caddy is running but returns 502

Cotton is not ready. Read sudo docker compose logs --tail=200 cotton and fix the first startup error, commonly the database password or storage permissions.

Cotton asks for the key after a restart

That is expected in the recommended browser-unlock mode. Use the same saved master key. Never generate a replacement key for existing encrypted data.

FAQ

Direct answers

Do I need to understand Docker before installing Cotton?

You do not need to be a Docker expert, but you should understand three things: containers run the app, volumes keep data across restarts, and deleting the wrong volume can delete real data.

Can I install Cotton on a home server?

Yes. A home server, mini PC, or NAS-like machine can work well when it stays online, has enough storage, and has a backup plan. Public access still needs HTTPS and a reverse proxy.

Do I need a domain name?

For local-only testing, no. For a public instance, use a domain and HTTPS so browsers, mobile clients, and desktop clients have one stable secure address.

Why does Cotton need PostgreSQL?

PostgreSQL stores structured metadata: accounts, folders, file records, shares, settings, and operational state. The large file content is stored separately as Cotton chunks.

Where are the actual files?

Cotton stores file content as chunks in the configured storage backend. That backend can be a persistent filesystem path or S3-compatible storage.

What is the difference between Caddy and Traefik?

Both are reverse proxies that can terminate HTTPS and forward requests to Cotton. Caddy is the shorter first-install path; Traefik is useful when an existing Docker host already routes many services through labels and shared middleware.

Does this page include working commands?

Yes. It provides one reproducible Ubuntu 24.04 VPS path, downloadable Compose and Caddy files, DNS and firewall steps, the first browser setup, update commands, a PostgreSQL backup command, and troubleshooting checks.