Skip to main content

NextCloud with Docker

We are going to create a PostgreSQL container, a NextCloud container (for the domain nc2.user1.fs.al), and a network where these two will be connected. We are going to use the existing nginx as a reverse proxy for this site.

1. Create a network

docker network ls
docker network create ncnet
docker network ls
docker network inspect ncnet | less

2. Create a PostgreSQL container

  1. We can download and use the official postgres image:

    docker search postgres | less
    docker pull postgres:16
    docker image ls
    docker image history postgres:16 | less
  2. Let's create a directory for this container:

    mkdir -p /var/docker/pg1
    cd /var/docker/pg1/
  3. Create a container:

    cat <<'_EOF_' > create.sh
    #!/bin/bash -x

    passwd='pass123'
    mkdir -p data
    docker create \
    --name pg1 \
    --hostname postgresql \
    --network ncnet \
    --network-alias pg1 \
    --mount type=bind,source=$(pwd)/data,destination=/var/lib/postgresql/data \
    --restart unless-stopped \
    --env POSTGRES_PASSWORD="$passwd" \
    postgres:16
    _EOF_
    nano create.sh
    chmod +x create.sh
    ./create.sh
    docker ps -a
  4. Start and test the container:

    docker start pg1
    docker ps
    docker logs pg1 -f
    ls data/
    docker exec -it pg1 bash
    whoami
    exit

    docker exec -it -u postgres pg1 psql
    \x
    \l
    \q
  5. Create a small script for running psql:

    cat <<'_EOF_' > psql.sh
    #!/bin/bash
    docker exec -it -u postgres pg1 psql "$@"
    _EOF_
    nano psql.sh
    chmod +x psql.sh
    ./psql.sh -c 'select 1;'
    ./psql.sh
    \q

3. Install NextCloud

Let's create a directory for it:

mkdir -p /var/docker/nc2
cd /var/docker/nc2/

3.1 Create database and user

  1. Create settings.sh with DB details:

    cat <<'_EOF_' > settings.sh
    DB_HOST="pg1"
    DB_NAME="nc2db"
    DB_USER="nc2user"
    DB_PASS="nc2pass"
    _EOF_
  2. Create script db-init.sh:

    cat <<'_EOF_' > db-init.sh
    #!/bin/bash -x

    source settings.sh

    psql="docker exec -i -u postgres $DB_HOST psql"
    cat <<EOF | tee /dev/tty | $psql
    create database $DB_NAME template template0 encoding 'UTF8';
    create user $DB_USER with password '$DB_PASS' createdb;
    alter database $DB_NAME owner to $DB_USER;
    grant all privileges on database $DB_NAME to $DB_USER;
    grant all privileges on schema public to $DB_USER;
    EOF

    _EOF_
  3. Run the script and create the database and user:

    nano db-init.sh
    chmod +x db-init.sh
    ./db-init.sh

    ../pg1/psql.sh -x -c "\l nc2db"
    ../pg1/psql.sh -x -c 'select * from pg_user'

3.2 Create a NextCloud container

  1. Pull the nextcloud image:

    docker search nextcloud | less
    docker pull nextcloud
    docker image ls
    docker image history nextcloud | less

    This image includes Apache2, PHP and the NC application files.

  2. Create a bash script for creating the container:

    cat <<'_EOF_' > create.sh
    #!/bin/bash -x

    source settings.sh

    mkdir -p www
    docker create \
    --name nc2 \
    --hostname nc2 \
    --network ncnet \
    --publish 127.0.0.1:8085:80 \
    --mount type=bind,source=$(pwd)/www,destination=/var/www/html \
    \
    --env POSTGRES_HOST=$DB_HOST \
    --env POSTGRES_DB=$DB_NAME \
    --env POSTGRES_USER=$DB_USER \
    --env POSTGRES_PASSWORD="$DB_PASS" \
    \
    --env NEXTCLOUD_ADMIN_USER=$ADMIN_USER \
    --env NEXTCLOUD_ADMIN_PASSWORD="$ADMIN_PASS" \
    \
    --env PHP_UPLOAD_LIMIT=$MAX_UPLOAD \
    --env PHP_MEMORY_LIMIT=$MAX_UPLOAD \
    --env APACHE_BODY_LIMIT=0 \
    \
    --env REDIS_HOST=redis1 \
    --env NEXTCLOUD_TRUSTED_DOMAINS=$DOMAIN \
    \
    --restart unless-stopped \
    nextcloud:latest

    # create a redis container as well, on the same network
    docker create \
    --name redis1 \
    --network ncnet \
    --restart unless-stopped \
    redis:latest
    _EOF_
    nano create.sh
  3. Append ADMIN_USER, ADMIN_PASS, MAX_UPLOAD and DOMAIN to settings.sh:

    apt install pwgen

    cat <<_EOF_ >> settings.sh

    ADMIN_USER=admin
    ADMIN_PASS=$(pwgen 15 1)

    MAX_UPLOAD=1024M
    DOMAIN="nc2.user1.fs.al"
    _EOF_

    nano settings.sh
  4. Run create.sh and start the containers:

    chmod +x create.sh
    ./create.sh
    docker ps -a

    docker start redis1
    docker logs redis1 -f

    docker start nc2
    docker logs nc2 -f
    ls www/
    ls -al www/
    ls www/config/
    nano www/config/config.php

3.3 Setup reverse-proxy

  1. Create a new site on the reverse proxy:

    cd /etc/nginx/sites-available/
    cp wp4 nc2
    sed -i nc2 -e 's/wp4/nc2/g'
    nano nc2
  2. Edit nc2 and make sure that the proxy_pass line looks like this:

    proxy_pass http://127.0.0.1:8085;

    Above proxy_pass add this line:

    client_max_body_size 1024M;

    Also, remove aliases www.nc2.user1.fs.al.

  3. Enable this site:

    cd ../sites-enabled/
    ln -s ../sites-available/nc2 .

    nginx -t
  4. Get an SSL cert and reload nginx:

    domain=nc2.user1.fs.al
    email=dashohoxha@gmail.com

    certbot certonly --webroot -w /var/www -d $domain --dry-run

    certbot certonly --webroot -w /var/www \
    -d $domain -m $email --agree-tos

    certbot certificates | less
  5. Reload nginx:

    nginx -t
    systemctl reload nginx
  6. Open in browser https://nc2.user1.fs.al and complete the installation wizard.

3.4 Improvements

  1. Create the script occ.sh:

    cd /var/docker/nc2/

    cat << '_EOF_' > occ.sh
    #!/bin/bash
    docker exec -it --user www-data nc2 php occ "$@"
    _EOF_

    chmod +x occ.sh
    ./occ.sh
  2. Let's fix some problems reported at https://nc2.user1.fs.al/settings/admin/overview

    cat << '_EOF_' > config.sh
    #!/bin/bash -x
    ./occ.sh config:system:set overwriteprotocol --value=https
    ./occ.sh config:system:set default_phone_region --value=AL
    ./occ.sh config:system:set maintenance_window_start --value=1 --type=integer
    ./occ.sh maintenance:repair --include-expensive
    ./occ.sh db:add-missing-indices
    _EOF_
    nano config.sh
    chmod +x config.sh
    ./config.sh
  3. To enable HSTS, edit /etc/nginx/sites-enabled/nc2 and add a line like this:

    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;

    Then reload nginx,

  4. To run cron jobs, edit crontab with crontab -e and append this line:

    */5 * * * * docker exec -u www-data nc2 php /var/www/html/cron.php

4. Maintenance

4.1 Backup

  1. Create a backup script:

    cat << '_EOF_' > ~/nc2-backup.sh
    #!/bin/bash -x

    occ="docker exec -it --user www-data nc2 php occ"
    pg_dump="docker exec -it -u postgres pg1 pg_dump"

    $occ maintenance:mode --on

    cd /var/docker/nc2/
    $pg_dump --clean -d nc2db > db.sql
    cd ..
    tar cfz nc2-$(date +%Y%m%d).tgz nc2/
    mv *.tgz ~/

    $occ maintenance:mode --off
    _EOF_
  2. Test it:

    nano ~/nc2-backup.sh
    chmod +x ~/nc2-backup.sh
    ~/nc2-backup.sh
    ls -lh ~/
    tar tvfz ~/nc2-20250210.tgz | less

4.2 Restore

  1. Uninstall NC2:

    cat <<'_EOF_' > ~/nc2-remove.sh
    #!/bin/bash -x

    docker stop nc2
    docker rm nc2

    echo "drop database nc2db;" \
    | docker exec -i -u postgres pg1 psql

    rm -rf /var/docker/nc2/
    _EOF_
    nano ~/nc2-remove.sh
    chmod +x ~/nc2-remove.sh
    ~/nc2-remove.sh
  2. Restore from backup:

    cat << '_EOF_' > ~/nc2-restore.sh
    #!/bin/bash -x

    [[ -z $1 ]] \
    && echo "Usage: $0 <backup-file.tgz>" >&2 \
    && exit 1
    [[ ! -f $1 ]] \
    && echo "Error: File '$1' does not exist." >&2 \
    && exit 2
    backup_file=$(realpath $1)

    systemctl stop nginx

    cd /var/docker/
    tar xfz $backup_file
    cd nc2/

    source settings.sh

    # recreate and restore the database
    psql="docker exec -i -u postgres $DB_HOST psql"
    echo "drop database $DB_NAME;" | $psql
    ./db-init.sh
    cat db.sql | $psql -d $DB_NAME

    # recreate and start the nextcloud and redis containers
    ./create.sh
    docker start redis1
    docker start nc2
    ./occ.sh maintenance:mode --off

    systemctl start nginx
    _EOF_
    nano ~/nc2-restore.sh
    chmod +x ~/nc2-restore.sh
    ~/nc2-restore.sh
    ~/nc2-restore.sh ~/nc2-20250210.tgz

4.3 Update

cd /var/docker/nc2/

cat << '_EOF_' > update.sh
#!/bin/bash -x

docker pull nextcloud
docker pull redis

docker stop nc2
docker rm nc2
docker stop redis1
docker rm redis1

./create.sh
docker start redis1
docker start nc2
_EOF_
nano update.sh
chmod +x update.sh
./update.sh

4.4 Upgrade

cat << '_EOF_' > upgrade.sh
#!/bin/bash -x

cd /var/docker/nc2/

systemctl stop nginx

./occ.sh upgrade
./occ.sh app:update --all
./occ.sh db:add-missing-indices
./occ.sh db:convert-filecache-bigint

systemctl start nginx
_EOF_
nano upgrade.sh
chmod +x upgrade.sh
./upgrade.sh