Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .docker/app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ FROM nextcloud:${NEXTCLOUD_VERSION}
RUN apt-get update \
&& apt-get install -y \
locales \
postgresql-client \
poppler-utils \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
&& locale-gen \
Expand Down
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
NEXTCLOUD_VERSION=30.0.17-fpm
NEXTCLOUD_VERSION=stable-fpm

VIRTUAL_HOST=
LETSENCRYPT_HOST=
Expand All @@ -8,6 +8,8 @@ LETSENCRYPT_EMAIL=
TZ=

POSTGRES_PASSWORD=
NEXTCLOUD_BACKUP_DIR=/backups
NEXTCLOUD_UPGRADE_MIN_FREE_MB=2048

NEXTCLOUD_ADMIN_USER=
NEXTCLOUD_ADMIN_PASSWORD=
Expand Down
64 changes: 23 additions & 41 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ on:
push:
branches:
- main
- develop
tags:
- 'v*'
pull_request:
branches:
- main
Expand All @@ -19,69 +16,54 @@ env:
jobs:
build:
runs-on: ubuntu-latest

permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4


- name: Read Nextcloud version
id: nextcloud_version
run: |
version=$(grep -m1 '^NEXTCLOUD_VERSION=' .env.example | cut -d= -f2-)
if [ -z "$version" ]; then
echo "NEXTCLOUD_VERSION is missing from .env.example" >&2
exit 1
fi
echo "value=$version" >> "$GITHUB_OUTPUT"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Container Registry
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (app)
id: meta_app
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_APP }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}

- name: Build and push Docker image (app)
- name: Build app image
uses: docker/build-push-action@v6
with:
context: .docker/app
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta_app.outputs.tags }}
labels: ${{ steps.meta_app.outputs.labels }}
push: ${{ github.event_name == 'push' }}
build-args: |
NEXTCLOUD_VERSION=${{ steps.nextcloud_version.outputs.value }}
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_APP }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Extract metadata (web)
id: meta_web
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_WEB }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}

- name: Build and push Docker image (web)
- name: Build web image
uses: docker/build-push-action@v6
with:
context: .docker/web
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta_web.outputs.tags }}
labels: ${{ steps.meta_web.outputs.labels }}
push: ${{ github.event_name == 'push' }}
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_WEB }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/volumes
/backups/*
!/backups/
!/backups/.gitkeep
.env
docker-compose.override.yml
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
COMPOSE ?= docker compose
GARAGES3_COMPOSE_FILE ?= docker-compose-garages3.yml

.PHONY: up-garages3 down-garages3 bootstrap-garages3 garage-status-garages3 start-garages3 wait-nextcloud-garages3 setup-garages3
.PHONY: up-garages3 down-garages3 bootstrap-garages3 garage-status-garages3 start-garages3 wait-nextcloud-garages3 setup-garages3 test-hooks

up-garages3:
$(COMPOSE) -f $(GARAGES3_COMPOSE_FILE) up -d garage
Expand All @@ -25,3 +25,6 @@ setup-garages3:
$(MAKE) bootstrap-garages3
$(MAKE) start-garages3
$(MAKE) wait-nextcloud-garages3

test-hooks:
bash tests/test-hooks.sh
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Languages avaliable: [pt-BR](docs/README_ptBR.md)
- [After setup](#after-setup)
- [Custom setup](#custom-setup)
- [Customize docker-compose content](#customize-docker-compose-content)
- [Nextcloud upgrade hooks](#nextcloud-upgrade-hooks)
- [PHP](#php)
- [Run Nextcloud](#run-nextcloud)
- [Use a specific version of Nextcloud](#use-a-specific-version-of-nextcloud)
Expand Down Expand Up @@ -106,6 +107,48 @@ docker compose exec -u www-data app ./occ db:convert-filecache-bigint

You can do this using environments and creating a file called `docker-compose.override.yml` to add new services.

### Redis

The main compose files now include a `redis` service by default. This keeps the stack self-contained for Nextcloud installations that already use Redis in `config.php` and avoids depending on a host-specific external network.

### Nextcloud upgrade hooks

This repository mounts the official Nextcloud Docker hook directories so you can extend install and upgrade flows without touching the image entrypoint.

The `app` service uses these mounts:

```yaml
services:
app:
volumes:
- ./volumes/nextcloud:/var/www/html
- ./backups:/backups
- ./app-hooks/pre-installation:/docker-entrypoint-hooks.d/pre-installation
- ./app-hooks/post-installation:/docker-entrypoint-hooks.d/post-installation
- ./app-hooks/pre-upgrade:/docker-entrypoint-hooks.d/pre-upgrade
- ./app-hooks/post-upgrade:/docker-entrypoint-hooks.d/post-upgrade
- ./app-hooks/before-starting:/docker-entrypoint-hooks.d/before-starting
```

The upgrade hooks behave like this:

- `pre-upgrade`: turns maintenance mode on
- `pre-upgrade`: saves the active app list to `/backups/app_list.old`
- `pre-upgrade`: checks free disk space on the Nextcloud volume and the backup volume
- `pre-upgrade`: creates a compressed PostgreSQL dump at `/backups/nextcloud-db.sql.gz`, replacing the previous dump
- `post-upgrade`: saves the new app list to `/backups/app_list.new` and prints a diff when possible
- `post-upgrade`: runs the extra `occ` commands needed after a major upgrade
- `post-upgrade`: turns maintenance mode off at the end

The following variables control the safety check and backup location:

- `NEXTCLOUD_BACKUP_DIR`, defaulting to `/backups`
- `NEXTCLOUD_UPGRADE_MIN_FREE_MB`, defaulting to `2048`

Both upgrade hooks use `NEXTCLOUD_BACKUP_DIR` for the app list files and the database dump.

This repository includes the `./backups` directory so Docker does not create it as a root-owned host path on a fresh checkout. It must still be writable by `www-data` inside the container. The recommended host-side ownership is `www-data:www-data` with mode `0755`.

### Garage S3 primary storage

Use `docker-compose-garages3.yml` when you want Nextcloud to store files in a Garage S3 bucket instead of the local `data/` directory.
Expand Down Expand Up @@ -185,6 +228,8 @@ docker compose -d

Change the value of NEXTCLOUD_VERSION at `.env` file and put the tag name that you want to use. Check the availables tags here: https://hub.docker.com/_/nextcloud/tags

The GHCR build workflow reads `NEXTCLOUD_VERSION` from `.env.example` and publishes the app and web images as static `:latest` tags so tools like Watchtower can track them reliably.

Build the images, down the containers and get up again:

```bash
Expand Down
1 change: 1 addition & 0 deletions app-hooks/before-starting/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

30 changes: 30 additions & 0 deletions app-hooks/post-upgrade/01-run-post-upgrade-commands.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

set -euo pipefail

backup_dir=${NEXTCLOUD_BACKUP_DIR:-/backups}

cleanup() {
php occ maintenance:mode --off >/dev/null 2>&1 || true
}

trap cleanup EXIT

run_occ() {
echo "Running: php occ $*"
php occ "$@"
}

echo "Running post-upgrade Nextcloud commands"
php occ app:list > "$backup_dir/app_list.new"
if [ -f "$backup_dir/app_list.old" ]; then
echo "Comparing app lists"
diff -u "$backup_dir/app_list.old" "$backup_dir/app_list.new" || true
fi
run_occ db:add-missing-columns
run_occ db:add-missing-indices
run_occ db:add-missing-primary-keys
run_occ maintenance:repair --include-expensive
run_occ config:system:set maintenance_window_start --type=integer --value=1
run_occ app:update --all
echo "Post-upgrade commands completed successfully"
1 change: 1 addition & 0 deletions app-hooks/pre-installation/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

72 changes: 72 additions & 0 deletions app-hooks/pre-upgrade/01-check-disk-and-dump-db.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash

set -euo pipefail

nextcloud_dir=${NEXTCLOUD_DIR:-/var/www/html}
backup_dir=${NEXTCLOUD_BACKUP_DIR:-/backups}
min_free_mb=${NEXTCLOUD_UPGRADE_MIN_FREE_MB:-2048}
db_host=${POSTGRES_HOST:-postgres}
db_name=${POSTGRES_DB:-nextcloud}
db_user=${POSTGRES_USER:-nextcloud}
db_password=${POSTGRES_PASSWORD:-}
backup_file="${backup_dir}/nextcloud-db.sql.gz"
backup_tmp_file="${backup_file}.tmp"
app_list_file="${backup_dir}/app_list.old"
maintenance_enabled=0

cleanup() {
if [ "$maintenance_enabled" -eq 1 ]; then
php occ maintenance:mode --off >/dev/null 2>&1 || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep maintenance enabled after pre-upgrade succeeds

Because this cleanup trap runs on every exit, a successful pre-upgrade turns maintenance mode off immediately after creating the dump. The Nextcloud Docker entrypoint runs pre-upgrade before its occ upgrade step and post-upgrade after that step, so this defeats the documented flow where pre-upgrade enables maintenance and post-upgrade disables it; the instance can be available during the upgrade gap and the post-upgrade repair/app-update commands run without maintenance mode.

Useful? React with 👍 / 👎.

fi

rm -f "$backup_tmp_file"
}

trap cleanup EXIT

run_occ() {
echo "Running: php occ $*"
php occ "$@"
}

check_free_space() {
local path=$1
local label=$2
local available_mb

available_mb=$(df -Pm "$path" | awk 'NR==2 { print $4 }')

if [ "$available_mb" -lt "$min_free_mb" ]; then
echo "Not enough disk space on ${label}: ${available_mb} MB available, ${min_free_mb} MB required"
exit 1
fi

echo "${label} has ${available_mb} MB free"
}

echo "Running pre-upgrade safety checks"
mkdir -p "$backup_dir"

run_occ maintenance:mode --on
maintenance_enabled=1
php occ app:list > "$app_list_file"
echo "Saved active apps list to ${app_list_file}"

check_free_space "$nextcloud_dir" "Nextcloud volume"
check_free_space "$backup_dir" "Backup volume"

if ! command -v pg_dump >/dev/null 2>&1; then
echo "pg_dump is not available in the container image"
exit 1
fi

if [ -z "$db_password" ]; then
echo "POSTGRES_PASSWORD is empty, refusing to create a database dump"
exit 1
fi

echo "Creating PostgreSQL dump at ${backup_file}"
PGPASSWORD="$db_password" pg_dump -h "$db_host" -U "$db_user" "$db_name" | gzip -9 > "$backup_tmp_file"
mv "$backup_tmp_file" "$backup_file"
rm -f "$backup_tmp_file"
echo "Database dump completed successfully"
1 change: 1 addition & 0 deletions backups/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

13 changes: 13 additions & 0 deletions docker-compose-garages3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ services:
networks:
- internal

redis:
image: redis:7-alpine
restart: unless-stopped
networks:
- internal

garage:
image: dxflrs/garage:v1.0.0
restart: unless-stopped
Expand All @@ -35,7 +41,12 @@ services:
restart: unless-stopped
volumes:
- ./volumes/nextcloud:/var/www/html
- ./backups:/backups
- ./app-hooks/pre-installation:/docker-entrypoint-hooks.d/pre-installation
- ./app-hooks/post-installation:/docker-entrypoint-hooks.d/post-installation
- ./app-hooks/pre-upgrade:/docker-entrypoint-hooks.d/pre-upgrade
- ./app-hooks/post-upgrade:/docker-entrypoint-hooks.d/post-upgrade
- ./app-hooks/before-starting:/docker-entrypoint-hooks.d/before-starting
- ./.docker/garages3.config.php:/var/www/html/config/garages3.config.php:ro
environment:
- POSTGRES_DB=${POSTGRES_DB:-nextcloud}
Expand Down Expand Up @@ -69,6 +80,7 @@ services:
- host.docker.internal:host-gateway
depends_on:
- db
- redis
- garage
networks:
- internal
Expand Down Expand Up @@ -115,6 +127,7 @@ services:
- host.docker.internal:host-gateway
depends_on:
- db
- redis
- garage
networks:
- internal
Expand Down
Loading
Loading