Hey there,
I wanted to install Nextcloud with docker compose manage by portainer and proxied by traefik. The database is postgres and additionally I am using redis.
At initial start everything seems to wokr, as I can login with my account and can do some stuff there. But if I stop the stack and start it again, I get an error, that the priviliges on oc_migration are insufficient.
But first things first.
This is my docker-compose yaml
networks:
nextcloud-network:
external: false
proxy:
external: true
volumes:
nextcloud-data:
redis-data:
nextcloud-postgres:
services:
postgres:
image: ${NEXTCLOUD_POSTGRES_IMAGE_TAG}
volumes:
- nextcloud-postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${NEXTCLOUD_DB_NAME}
POSTGRES_USER: ${NEXTCLOUD_DB_USER}
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
networks:
- nextcloud-network
healthcheck:
test: [ "CMD", "pg_isready", "-q", "-d", "${NEXTCLOUD_DB_NAME}", "-U", "${NEXTCLOUD_DB_USER}" ]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
restart: unless-stopped
redis:
image: redis:alpine
command: ["redis-server", "--requirepass", "$NEXTCLOUD_REDIS_PASSWORD"]
volumes:
- redis-data:/data
networks:
- nextcloud-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
restart: unless-stopped
nextcloud:
image: ${NEXTCLOUD_IMAGE_TAG}
volumes:
- nextcloud-data:${DATA_PATH}
environment:
TZ: Europe/Berlin
NC_SETUP_CREATE_DB_USER: false
POSTGRES_HOST: postgres
DB_PORT: 5432
POSTGRES_DB: ${NEXTCLOUD_DB_NAME}
POSTGRES_USER: ${NEXTCLOUD_DB_USER}
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
REDIS_HOST: redis
REDIS_HOST_PORT: 6379
REDIS_HOST_PASSWORD: ${NEXTCLOUD_REDIS_PASSWORD}
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USERNAME}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_HOSTNAME}
OVERWRITECLIURL: ${NEXTCLOUD_URL}
OVERWRITEPROTOCOL: https
OVERWRITEHOST: ${NEXTCLOUD_HOSTNAME}
TRUSTED_PROXIES: 172.16.0.0/12 192.168.0.0/16 10.0.0.0/8 fc00::/7 fe80::/10 2001:db8::/32 sub.server.local sub.myzone.dedyn.io 192.168.178.0/24
networks:
- nextcloud-network
- proxy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 10s
timeout: 5s
retries: 3
start_period: 90s
ports:
- "8081:80"
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.service=nextcloud"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.rule=Host(`sub.server.local`) || Host(`sub.myzone.dedyn.io`)"
- "traefik.http.middlewares.nc-rep.redirectregex.regex=https://(.*)/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nc-rep.redirectregex.replacement=https://$$1/remote.php/dav/"
- "traefik.http.middlewares.nc-rep.redirectregex.permanent=true"
- "traefik.http.middlewares.nc-header.headers.customFrameOptionsValue=SAMEORIGIN"
- "traefik.http.middlewares.nc-header.headers.customResponseHeaders.Strict-Transport-Security=15552000"
- "traefik.http.routers.nextcloud.tls=true"
- "traefik.http.routers.nextcloud.tls.domains[0].main=myzone.dedyn.io"
- "traefik.http.routers.nextcloud.tls.domains[0].sans=*.myzone.dedyn.io"
- "traefik.http.routers.nextcloud.tls.certresolver=desec"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
- "traefik.http.services.nextcloud.loadbalancer.passHostHeader=true"
- "traefik.http.routers.nextcloud.middlewares=nextcloud-redirectregex1,nextcloud-redirectregex2,compresstraefik,nextcloud-securityheaders"
# Define settings for the compression middleware
- "traefik.http.middlewares.compresstraefik.compress=true"
# Settings for the first redirect regex middleware
- "traefik.http.middlewares.nextcloud-redirectregex1.redirectregex.permanent=true"
- "traefik.http.middlewares.nextcloud-redirectregex1.redirectregex.regex=https?://([^/]*)/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nextcloud-redirectregex1.redirectregex.replacement=https://$${1}/remote.php/dav/"
# Settings for the second redirect regex middleware
- "traefik.http.middlewares.nextcloud-redirectregex2.redirectregex.permanent=true"
- "traefik.http.middlewares.nextcloud-redirectregex2.redirectregex.regex=https?://([^/]*)(/.well-known[^#]*)"
- "traefik.http.middlewares.nextcloud-redirectregex2.redirectregex.replacement=https://$${1}/index.php$${2}"
# Security headers settings specifically for Nextcloud
- "traefik.http.middlewares.nextcloud-securityheaders.headers.stsSeconds=15552000"
- "traefik.http.middlewares.nextcloud-securityheaders.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.nextcloud-securityheaders.headers.stsPreload=true"
# Specify which Docker network Traefik should use for routing
- "traefik.docker.network=proxy"
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
nextcloud-cron:
image: ${NEXTCLOUD_IMAGE_TAG}
entrypoint: /cron.sh
volumes:
- nextcloud-data:${DATA_PATH}
networks:
- nextcloud-network
This is the env file
NEXTCLOUD_DB_NAME=nextcloud_db
NEXTCLOUD_DB_USER=nextcloud_user
NEXTCLOUD_DB_PASSWORD=nextcloud_db_pw
DATA_PATH=/mnt/extern/ncdata
NEXTCLOUD_POSTGRES_IMAGE_TAG=postgres
NEXTCLOUD_REDIS_PASSWORD=redis_pw
NEXTCLOUD_ADMIN_USERNAME=horst
NEXTCLOUD_ADMIN_PASSWORD=hosts_passwort
NEXTCLOUD_HOSTNAME=sub.myzone.dedyn.io
NEXTCLOUD_IMAGE_TAG=nextcloud
NEXTCLOUD_URL=https://sub.myzone.dedyn.io
And this is my traefik:
# log default is ERROR, but INFO is more helpful
log:
level: DEBUG
# enable dashboard on 8080 with NO AUTH
api:
insecure: true
dashboard: true
# enable ping so the `traefik healthcheck` works
ping: {}
metrics:
prometheus:
entryPoint: metrics
headerLabels:
label: headerKey
accessLog: {}
# auto-proxy containers if they have proper labels
# and also use this file for dynamic config (tls)
providers:
docker:
exposedByDefault: false
watch: true
# network: gateway
file:
fileName: /etc/traefik/traefik.yaml
filename: /etc/traefik/rules.yaml
watch: true
# listen on 80/443, and redirect all 80 to 443 via 301
entryPoints:
metrics:
address: :8082
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: :443
tls:
certResolver: desec
# Certificates
certificatesResolvers:
desec:
acme:
email: my@mail.de
storage: /letsencrypt/acme.json
# caserver: https://acme-v02.api.letsencrypt.org/directory
dnschallenge:
provider: desec
delayBeforecheck: 0
resolvers:
- ns1.desec.io.:53
- ns2.desec.org.:53
Proof that it works after the stack starts the first time:
Now I stop the stack via portainer
After starting the stack again, postgres displays this log
PostgreSQL Database directory appears to contain a database; Skipping initialization
2025-04-28 19:17:02.031 UTC [1] LOG: starting PostgreSQL 17.4 (Debian 17.4-1.pgdg120+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
2025-04-28 19:17:02.031 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2025-04-28 19:17:02.031 UTC [1] LOG: listening on IPv6 address "::", port 5432
2025-04-28 19:17:02.036 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2025-04-28 19:17:02.046 UTC [30] LOG: database system was shut down at 2025-04-28 19:16:48 UTC
2025-04-28 19:17:02.053 UTC [1] LOG: database system is ready to accept connections
2025-04-28 19:17:11.978 UTC [44] ERROR: permission denied for table oc_migrations
2025-04-28 19:17:11.978 UTC [44] STATEMENT: SELECT "version" FROM "oc_migrations" WHERE "app" = $1 ORDER BY "version" ASC
This is what nextcloud displays in logs:
Previous: PDOException: SQLSTATE[42501]: Insufficient privilege: 7 ERROR: permission denied for table oc_migrations
Trace: #0 /var/www/html/3rdparty/doctrine/dbal/src/Driver/PDO/Statement.php(130): PDOStatement->execute(NULL)
#1 /var/www/html/3rdparty/doctrine/dbal/src/Connection.php(1104): Doctrine\DBAL\Driver\PDO\Statement->execute()
#2 /var/www/html/lib/private/DB/Connection.php(419): Doctrine\DBAL\Connection->executeQuery('SELECT "version...', Array, Array, NULL)
#3 /var/www/html/lib/private/DB/ConnectionAdapter.php(50): OC\DB\Connection->executeQuery('SELECT "version...', Array, Array)
#4 /var/www/html/lib/private/DB/QueryBuilder/QueryBuilder.php(289): OC\DB\ConnectionAdapter->executeQuery('SELECT `version...', Array, Array)
#5 /var/www/html/lib/private/DB/MigrationService.php(173): OC\DB\QueryBuilder\QueryBuilder->executeQuery()
#6 /var/www/html/lib/private/DB/MigrationService.php(248): OC\DB\MigrationService->getMigratedVersions()
#7 /var/www/html/lib/private/DB/MigrationService.php(410): OC\DB\MigrationService->getMigrationsToExecute('latest')
#8 /var/www/html/lib/private/DB/MigrationService.php(387): OC\DB\MigrationService->migrateSchemaOnly('latest')
#9 /var/www/html/lib/private/Setup/AbstractDatabase.php(140): OC\DB\MigrationService->migrate('latest', true)
#10 /var/www/html/lib/private/Setup.php(319): OC\Setup\AbstractDatabase->runMigrations(NULL)
#11 /var/www/html/core/Command/Maintenance/Install.php(80): OC\Setup->install(Array, NULL)
#12 /var/www/html/3rdparty/symfony/console/Command/Command.php(326): OC\Core\Command\Maintenance\Install->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#13 /var/www/html/3rdparty/symfony/console/Application.php(1078): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#14 /var/www/html/3rdparty/symfony/console/Application.php(324): Symfony\Component\Console\Application->doRunCommand(Object(OC\Core\Command\Maintenance\Install), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#15 /var/www/html/3rdparty/symfony/console/Application.php(175): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#16 /var/www/html/lib/private/Console/Application.php(187): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#17 /var/www/html/console.php(87): OC\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput))
#18 /var/www/html/occ(33): require_once('/var/www/html/c...')
#19 {main}
Retrying install...
After the container stops reinstalling and allows me to access the web page, I get this
I entered he postgres password below but it did not help, as I get back to the insallation screen you can see above.
What did I already do:
I tried to stop the containers sequentially and postgres as last one. This seemed to work once or twice, but the error appeared again after a while.
I changed the credentials to easier ones and used another datadir, without success.
I grranted permission to the db user in this way, but it seems, that it does not grant the permission, the permission is not persisted, or is not the solution:
docker-compose exec postgres psql -U `${{NEXTCLOUD_DB_USER} -d}$`{NEXTCLOUD_DB_NAME}
Berechtigungen erteilen:
GRANT ALL PRIVILEGES ON DATABASE `${{NEXTCLOUD_DB_NAME} TO}$`{NEXTCLOUD_DB_USER};
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ${NEXTCLOUD_DB_USER};
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ${NEXTCLOUD_DB_USER};
I googled for this error but did not find any applicable solution and hoped, that you would be able to help me.
Thank you very much :-*