Skip to content

Nextcloud

Nextcloud is the single-domain file and groupware app on my server. Two ways to deploy it: All-in-One (AIO), a managed container set with a web interface, or plain docker compose, which is what I use since I already run compose for everything and just add Collabora or OnlyOffice myself. Because Nextcloud accepts exactly one domain, it has to be paired with Context-Aware DNS so that domain resolves correctly on the LAN, the tailnet, and the public internet.

Option A — All-in-One (AIO)

AIO is a container-management layer for Nextcloud's official containers. Launch the master container following the reverse-proxy docs, with these adjustments:

sudo docker run -d \
  --init \
  --sig-proxy=false \
  --name nextcloud-aio-mastercontainer \
  --restart always \
  --publish 127.0.0.1:8089:8080 \        # bind to localhost, don't expose directly
  --env APACHE_PORT=11000 \
  --env APACHE_IP_BINDING=0.0.0.0 \
  --env APACHE_ADDITIONAL_NETWORK="" \
  --env SKIP_DOMAIN_VALIDATION=true \     # domain validation fails for local/tailnet IPs
  --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \  # must be a docker volume, not a bind mount
  --volume /var/run/docker.sock:/var/run/docker.sock:ro \          # AIO needs the sock to create containers
  ghcr.io/nextcloud-releases/all-in-one:latest

Access the AIO interface through the reverse proxy (not the raw port — poor practice). Proxy it like any app; note Nextcloud warns HSTS could break the AIO UI in the future, so comment out the HSTS include for that block if it ever misbehaves.

The trade-off: if you run your apps with compose, AIO is annoying — you manage containers through its interface instead of your normal compose workflow.

Option B — plain docker compose (preferred)

Standard nextcloud compose with bind mounts driven by .env:

services:
  db:
    image: mariadb:lts
    restart: always
    command: --transaction-isolation=READ-COMMITTED
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PW}
      - MYSQL_PASSWORD=${DB_PW}
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=${DB_USER}

  redis:
    image: redis:alpine
    restart: always

  app:
    image: nextcloud
    restart: always
    ports:
      - 12000:80
    depends_on: [redis, db]
    volumes:
      - ${NC_DATA_LOCATION}:/var/www/html
    dns:
      - 192.168.4.100     # the local DNS server, see Context-Aware DNS

Add Collabora yourself (OnlyOffice optional): collabora/code with DONT_GEN_SSL_CERT=YES and --o:ssl.enable=false --o:ssl.termination=true since Nginx terminates TLS, plus aliasgroup1 for the allowed hosts.

Config

  • config.php → trusted_domains must list every way the site is reached: localhost, the server IP, and nextcloud.<domain>.com — otherwise access outside the host's localhost errors out.
  • One domain only — which is why context-aware DNS is needed for it.

See also