Co-Authored-By: Oz <oz-agent@warp.dev>
Nextcloud on Ubuntu via Docker for nxt.bhatfamily.in
This repository deploys Nextcloud behind Nginx using Docker Compose.
Exposed ports:
- HTTP:
8082 - HTTPS:
8446
Target hostname:
nxt.bhatfamily.in
What changed
The stack now includes:
- Fixed Nginx mount path (
nginx/nginx.confmapped correctly) - Fixed MariaDB command (
mariadbd) - Nginx reverse proxy mode for
nextcloud:apache(no FastCGI mismatch) - Production TLS provisioning using Let's Encrypt DNS-01 with Cloudflare
- Automated TLS renewal job support (cron)
- Nextcloud app startup fixes for Apache
ServerNameand writable Fontconfig cache - Persistent web-updater enablement (
upgrade-disable-web.config.phpforced tofalse) - Reverse-proxy trust configuration in Nextcloud (
trusted_proxies,forwarded_for_headers) - Nginx hardening (
server_tokens off, stronger HSTS, hideX-Powered-By, TLS session hardening) - Brute-force protection explicitly enabled and maintenance window configured
- Scripted Nextcloud Hub upgrade workflow:
scripts/update-nextcloud-hub.sh - Automated Hub update scheduler:
scripts/setup-hub-update-cron.sh - Redis service for transactional file locking and distributed cache
- Floating app image tag enabled:
nextcloud:apache(major upgrades supported with staged path) - Setup warning remediation integrated (missing indices, mimetype migrations, log-noise cleanup)
Current baseline (Apr 2026)
- App image:
nextcloud:apache - DB image:
mariadb:11.4 - Cache/locking:
redis:7-alpine - Nextcloud version at last validation:
33.0.2
Prerequisites
- Ubuntu host with Docker + Docker Compose plugin (or
docker-compose) - Domain
nxt.bhatfamily.inin Cloudflare DNS - DNS A record for
nxtpointing to your server public IP (DNS-only) - Router/firewall forwarding for ports
8082and8446
Initial setup
- Create runtime env file:
cp .env.example .env
- Edit
.envwith strong values. - Start stack with bootstrap TLS:
./scripts/install.sh
- Validate:
./scripts/test.sh
Enable web updater (one-time verification)
Web updater is enabled by design for this deployment.
Check values:
docker exec --user www-data nextcloud-app php occ config:list system | grep -E "upgrade.disable-web|updater.release.channel"
Expected:
upgrade.disable-web: falseupdater.release.channel: stable
Update Nextcloud Hub (scripted)
Use the upgrade helper script to pull images, apply upgrades, run repairs, and validate endpoints.
Run update:
./scripts/update-nextcloud-hub.sh
Optional flags:
- Skip app marketplace updates:
RUN_APP_UPDATES=0 ./scripts/update-nextcloud-hub.sh
- Skip expensive repairs/mimetype migration pass:
RUN_EXPENSIVE_REPAIR=0 ./scripts/update-nextcloud-hub.sh
- Require strict TLS validation during smoke tests (no
-k):
STRICT_TLS=1 ./scripts/update-nextcloud-hub.sh
What the script does:
- pulls latest
db,app, andwebimages - recreates services and restarts
webto refresh upstream resolution - ensures web updater is enabled and release channel is configured
- ensures maintenance mode is off before
occ upgrade - runs
occ upgrade - runs
occ app:update --all(unless disabled) - runs schema and repair commands (
db:add-missing-*,maintenance:repair) - optionally runs
maintenance:repair --include-expensive - runs
occ status,occ setupchecks, andscripts/test.sh
Important: major upgrades are one-at-a-time
Nextcloud only supports upgrading one major version at a time.
If the floating nextcloud:apache tag is more than one major ahead of your installed version, do staged upgrades first, for example:
nextcloud:31-apache→ run updater scriptnextcloud:32-apache→ run updater scriptnextcloud:33-apache→ run updater script- switch back to
nextcloud:apache
Automate Hub updates (cron)
Install/update a weekly cron job (default: Sunday 02:30):
./scripts/setup-hub-update-cron.sh
Optional custom schedule:
HUB_UPDATE_CRON_SCHEDULE="15 2 * * 6" ./scripts/setup-hub-update-cron.sh
This setup script will:
- create/update
.hub-update.env(local only, not committed) - install/refresh cron entry for
scripts/update-nextcloud-hub.sh - write logs to
logs/hub-update.log
Defaults tracked in .hub-update.env.example:
RUN_APP_UPDATES=1RUN_EXPENSIVE_REPAIR=1STRICT_TLS=0ENABLE_WEB_UPDATER=1UPDATER_RELEASE_CHANNEL=stable
Setup warning remediation notes
The following warning-focused fixes are now part of the deployed configuration:
- Transactional File Locking: Redis-backed locking enabled (
memcache.locking=Redis) - Mimetype migrations: addressed via
maintenance:repair --include-expensive - Missing optional indices: addressed via
occ db:add-missing-indices - AppAPI deploy daemon errors:
app_apidisabled (not used in this deployment) - Internet connectivity check log spam:
has_internet_connection=falseset intentionally for this environment
If you want AppAPI external apps later:
- re-enable app:
docker exec --user www-data nextcloud-app php occ app:enable app_api - configure a reachable deploy daemon from Settings > AppAPI
Move Nextcloud data directory to external storage
Use the migration helper to move existing data to a host path and switch the app to a bind mount.
Default target:
/media/rbhat/DATA/nextcloud/NextCloudData
Run migration:
./scripts/migrate-data-directory.sh /media/rbhat/DATA/nextcloud/NextCloudData
What the script does:
- enables maintenance mode
- copies current
/var/www/html/datacontent to target directory - applies owner/group and permissions for Nextcloud (
www-data) - updates
docker-compose.ymlapp volume with...:/var/www/html/data - recreates
appandwebservices - disables maintenance mode and verifies mount
Rollback (if needed):
- Remove the
:/var/www/html/databind mount line fromappvolumes indocker-compose.yml. docker compose up -d app web- Confirm status:
docker exec --user www-data nextcloud-app php occ status
Production TLS (Let's Encrypt + Cloudflare DNS-01)
- Export credentials in shell:
export CF_DNS_API_TOKEN={{CF_DNS_API_TOKEN}}
export LETSENCRYPT_EMAIL={{LETSENCRYPT_EMAIL}}
- Issue/renew and install production cert:
./scripts/provision-production-tls.sh
- Reload Nginx container:
docker compose restart web
- Verify cert:
echo | openssl s_client -connect nxt.bhatfamily.in:8446 -servername nxt.bhatfamily.in 2>/dev/null | openssl x509 -noout -subject -issuer -dates
Automated renewal job (cron)
- Ensure your Cloudflare token export script exists (default path used by renewal wrapper):
~/bin/cloudflare-api-usertoken.sh
- Install/update renewal cron entry:
./scripts/setup-renewal-cron.sh
This script will:
- create/update
.tls-renewal.env(local only, not committed) - install a daily cron job (
03:17by default) - write logs to
logs/tls-renew.log
- Manual renewal run (same path cron uses):
./scripts/renew-production-tls.sh
Admin password reset
List existing users:
docker exec --user www-data nextcloud-app php occ user:list
Reset password using helper script (interactive prompt):
./scripts/reset-admin-password.sh admin
Reset password non-interactively (for automation):
NEW_NEXTCLOUD_PASSWORD={{NEW_NEXTCLOUD_PASSWORD}} ./scripts/reset-admin-password.sh admin
You can target a different username by passing it as the first argument.
Useful commands
Start/update containers:
docker compose up -d
Restart all services:
docker compose restart
Restart web only:
docker compose restart web
Stop and remove containers/volumes:
./scripts/uninstall.sh
Security notes
.env,.tls-renewal.env,.hub-update.env, and runtime cert material undernginx/sslare intentionally ignored by Git.backups/is ignored and used for local database/log snapshots before risky changes.- Keep
.envmode restricted (chmod 600 .env). - If secrets were ever committed earlier, rotate them.