|
| 1 | +# syntax=docker/dockerfile:1 |
| 2 | + |
| 3 | +# --------------------------------------------------------------------------- |
| 4 | +# Build arguments |
| 5 | +# |
| 6 | +# NEXTCLOUD_VERSION : version to download from the community CDN |
| 7 | +# ENTERPRISE_ARCHIVE_URL : if set, download the enterprise zip from this URL instead of the community tarball. |
| 8 | +# --------------------------------------------------------------------------- |
| 9 | +ARG NEXTCLOUD_VERSION= |
| 10 | +ARG ENTERPRISE_ARCHIVE_URL="" |
| 11 | + |
| 12 | +# --------------------------------------------------------------------------- |
| 13 | +# Stage: source |
| 14 | +# Downloads and extracts the Nextcloud source once, at build time. |
| 15 | +# No rsync, no runtime downloads. |
| 16 | +# --------------------------------------------------------------------------- |
| 17 | +FROM alpine:3.23 AS source |
| 18 | + |
| 19 | +ARG NEXTCLOUD_VERSION |
| 20 | +ARG ENTERPRISE_ARCHIVE_URL |
| 21 | + |
| 22 | +RUN apk add --no-cache \ |
| 23 | + curl \ |
| 24 | + gnupg \ |
| 25 | + bzip2 \ |
| 26 | + unzip |
| 27 | + |
| 28 | +# Community: download + GPG verify + extract |
| 29 | +# Enterprise: download zip (URL already embeds the customer token) + extract |
| 30 | +RUN set -ex; \ |
| 31 | + mkdir -p /var/www/html; \ |
| 32 | + if [ -n "${ENTERPRISE_ARCHIVE_URL}" ]; then \ |
| 33 | + echo "==> Downloading Nextcloud Enterprise from ${ENTERPRISE_ARCHIVE_URL}"; \ |
| 34 | + curl -fsSL -o /tmp/nextcloud.zip "${ENTERPRISE_ARCHIVE_URL}"; \ |
| 35 | + unzip -q /tmp/nextcloud.zip -d /tmp/src/; \ |
| 36 | + mv /tmp/src/nextcloud/* /var/www/html/; \ |
| 37 | + rm /tmp/nextcloud.zip; \ |
| 38 | + else \ |
| 39 | + echo "==> Downloading Nextcloud Community ${NEXTCLOUD_VERSION}"; \ |
| 40 | + curl -fsSL -o /tmp/nextcloud.tar.bz2 \ |
| 41 | + "https://github.com/nextcloud-releases/server/releases/download/v${NEXTCLOUD_VERSION}/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2"; \ |
| 42 | + curl -fsSL -o /tmp/nextcloud.tar.bz2.asc \ |
| 43 | + "https://github.com/nextcloud-releases/server/releases/download/v${NEXTCLOUD_VERSION}/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2.asc"; \ |
| 44 | + export GNUPGHOME="$(mktemp -d)"; \ |
| 45 | + gpg --batch --keyserver keyserver.ubuntu.com \ |
| 46 | + --recv-keys 28806A878AE423A28372792ED75899B9A724937A; \ |
| 47 | + gpg --batch --verify /tmp/nextcloud.tar.bz2.asc /tmp/nextcloud.tar.bz2; \ |
| 48 | + gpgconf --kill all; \ |
| 49 | + rm -rf "${GNUPGHOME}"; \ |
| 50 | + tar -xjf /tmp/nextcloud.tar.bz2 -C /tmp/src/; \ |
| 51 | + mv /tmp/src/nextcloud /var/www/html; \ |
| 52 | + rm /tmp/nextcloud.tar.bz2 /tmp/nextcloud.tar.bz2.asc; \ |
| 53 | + fi; \ |
| 54 | + # Remove the built-in web updater — updates happen via image rebuild |
| 55 | + rm -rf /var/www/html/updater; \ |
| 56 | + # Ensure these dirs exist for first-run volume mount detection |
| 57 | + mkdir -p \ |
| 58 | + /var/www/html/data \ |
| 59 | + /var/www/html/custom_apps \ |
| 60 | + /var/www/html/config \ |
| 61 | + /var/www/html/themes; \ |
| 62 | + chmod +x /var/www/html/occ |
| 63 | + |
| 64 | +# Copy default config files into the source tree so they land in the image |
| 65 | +COPY config/ /var/www/html/config/ |
| 66 | + |
| 67 | +# --------------------------------------------------------------------------- |
| 68 | +# Stage: final |
| 69 | +# --------------------------------------------------------------------------- |
| 70 | +FROM php:8.4-apache-trixie |
| 71 | + |
| 72 | +ENV PHP_MEMORY_LIMIT=512M \ |
| 73 | + PHP_UPLOAD_LIMIT=512M \ |
| 74 | + PHP_OPCACHE_MEMORY_CONSUMPTION=128 \ |
| 75 | + APACHE_BODY_LIMIT=1073741824 |
| 76 | + |
| 77 | +RUN set -ex; \ |
| 78 | + savedAptMark="$(apt-mark showmanual)"; \ |
| 79 | + apt-get update; \ |
| 80 | + apt-get install -y --no-install-recommends \ |
| 81 | + # entrypoint.sh and cron.sh dependencies |
| 82 | + busybox-static \ |
| 83 | + bzip2 \ |
| 84 | + libldap-common \ |
| 85 | + libmagickcore-7.q16-10-extra \ |
| 86 | + # install the PHP extensions we need |
| 87 | + # see https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html |
| 88 | + libcurl4-openssl-dev \ |
| 89 | + libevent-dev \ |
| 90 | + libfreetype6-dev \ |
| 91 | + libgmp-dev \ |
| 92 | + libicu-dev \ |
| 93 | + libjpeg-dev \ |
| 94 | + libldap2-dev \ |
| 95 | + liblz4-dev \ |
| 96 | + libmagickwand-dev \ |
| 97 | + libmemcached-dev \ |
| 98 | + libpng-dev \ |
| 99 | + libpq-dev \ |
| 100 | + libwebp-dev \ |
| 101 | + libxml2-dev \ |
| 102 | + libzip-dev \ |
| 103 | + ; \ |
| 104 | + debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ |
| 105 | + docker-php-ext-configure ftp --with-ftp-ssl; \ |
| 106 | + docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \ |
| 107 | + docker-php-ext-configure ldap --with-libdir="lib/${debMultiarch}"; \ |
| 108 | + docker-php-ext-install -j "$(nproc)" \ |
| 109 | + bcmath \ |
| 110 | + exif \ |
| 111 | + ftp \ |
| 112 | + gd \ |
| 113 | + gmp \ |
| 114 | + intl \ |
| 115 | + ldap \ |
| 116 | + opcache \ |
| 117 | + pcntl \ |
| 118 | + pdo_mysql \ |
| 119 | + pdo_pgsql \ |
| 120 | + sysvsem \ |
| 121 | + zip \ |
| 122 | + ; \ |
| 123 | + # pecl will claim success even if one install fails, so we need to perform each install separately |
| 124 | + pecl install APCu-5.1.28; \ |
| 125 | + pecl install igbinary-3.2.16; \ |
| 126 | + pecl install imagick-3.8.1; \ |
| 127 | + pecl install --configureoptions 'enable-memcached-igbinary="yes"' \ |
| 128 | + memcached-3.4.0; \ |
| 129 | + pecl install --configureoptions 'enable-redis-igbinary="yes" enable-redis-zstd="yes" enable-redis-lz4="yes"' \ |
| 130 | + redis-6.3.0; \ |
| 131 | + docker-php-ext-enable \ |
| 132 | + apcu \ |
| 133 | + igbinary \ |
| 134 | + imagick \ |
| 135 | + memcached \ |
| 136 | + opcache \ |
| 137 | + redis \ |
| 138 | + ; \ |
| 139 | + rm -r /tmp/pear; \ |
| 140 | + # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies |
| 141 | + apt-mark auto '.*' > /dev/null; \ |
| 142 | + apt-mark manual $savedAptMark; \ |
| 143 | + ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ |
| 144 | + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ |
| 145 | + | sort -u \ |
| 146 | + | xargs -rt dpkg-query --search \ |
| 147 | + # https://manpages.debian.org/trixie/dpkg/dpkg-query.1.en.html#S (we ignore diversions and it'll be really unusual for more than one package to provide any given .so file) |
| 148 | + | awk 'sub(":$", "", $1) { print $1 }' \ |
| 149 | + | sort -u \ |
| 150 | + | xargs -rt apt-mark manual; \ |
| 151 | + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ |
| 152 | + apt-get clean; \ |
| 153 | + rm -rf /var/lib/apt/lists/* |
| 154 | + |
| 155 | +# set recommended PHP.ini settings |
| 156 | +# see https://docs.nextcloud.com/server/latest/admin_manual/installation/server_tuning.html#enable-php-opcache |
| 157 | +RUN { \ |
| 158 | + echo 'opcache.enable=1'; \ |
| 159 | + echo 'opcache.interned_strings_buffer=32'; \ |
| 160 | + echo 'opcache.max_accelerated_files=10000'; \ |
| 161 | + echo 'opcache.memory_consumption=${PHP_OPCACHE_MEMORY_CONSUMPTION}'; \ |
| 162 | + echo 'opcache.save_comments=1'; \ |
| 163 | + echo 'opcache.revalidate_freq=60'; \ |
| 164 | + echo 'opcache.jit=1255'; \ |
| 165 | + echo 'opcache.jit_buffer_size=8M'; \ |
| 166 | + } > "${PHP_INI_DIR}/conf.d/opcache-recommended.ini"; \ |
| 167 | + \ |
| 168 | + echo 'apc.enable_cli=1' >> "${PHP_INI_DIR}/conf.d/docker-php-ext-apcu.ini"; \ |
| 169 | + \ |
| 170 | + { \ |
| 171 | + echo 'apc.serializer=igbinary'; \ |
| 172 | + echo 'session.serialize_handler=igbinary'; \ |
| 173 | + } >> "${PHP_INI_DIR}/conf.d/docker-php-ext-igbinary.ini"; \ |
| 174 | + \ |
| 175 | + { \ |
| 176 | + echo 'memory_limit=${PHP_MEMORY_LIMIT}'; \ |
| 177 | + echo 'upload_max_filesize=${PHP_UPLOAD_LIMIT}'; \ |
| 178 | + echo 'post_max_size=${PHP_UPLOAD_LIMIT}'; \ |
| 179 | + } > "${PHP_INI_DIR}/conf.d/nextcloud.ini"; |
| 180 | + |
| 181 | +# ---- Apache ---------------------------------------------------------------- |
| 182 | +RUN a2enmod headers rewrite remoteip; \ |
| 183 | + { \ |
| 184 | + echo 'RemoteIPHeader X-Real-IP'; \ |
| 185 | + echo 'RemoteIPInternalProxy 10.0.0.0/8'; \ |
| 186 | + echo 'RemoteIPInternalProxy 172.16.0.0/12'; \ |
| 187 | + echo 'RemoteIPInternalProxy 192.168.0.0/16'; \ |
| 188 | + } > /etc/apache2/conf-available/remoteip.conf; \ |
| 189 | + a2enconf remoteip; \ |
| 190 | + { \ |
| 191 | + echo 'LimitRequestBody ${APACHE_BODY_LIMIT}'; \ |
| 192 | + } > /etc/apache2/conf-available/apache-limits.conf; \ |
| 193 | + a2enconf apache-limits |
| 194 | + |
| 195 | +# ---- cron ------------------------------------------------------------------ |
| 196 | +RUN mkdir -p /var/spool/cron/crontabs; \ |
| 197 | + echo '*/5 * * * * php -f /var/www/html/cron.php' > /var/spool/cron/crontabs/www-data |
| 198 | + |
| 199 | +# ---- hook directories ------------------------------------------------------ |
| 200 | +RUN mkdir -p \ |
| 201 | + /docker-entrypoint-hooks.d/pre-installation \ |
| 202 | + /docker-entrypoint-hooks.d/post-installation \ |
| 203 | + /docker-entrypoint-hooks.d/pre-upgrade \ |
| 204 | + /docker-entrypoint-hooks.d/post-upgrade \ |
| 205 | + /docker-entrypoint-hooks.d/before-starting |
| 206 | + |
| 207 | +# ---- Nextcloud source (from build stage) ----------------------------------- |
| 208 | +# The code is baked into the image — no VOLUME on /var/www/html. |
| 209 | +# Only user-data directories are declared as volumes so they persist across |
| 210 | +# image updates without touching the application code. |
| 211 | +COPY --from=source --chown=www-data:root /var/www/html /var/www/html |
| 212 | + |
| 213 | +RUN chmod -R g=u /var/www/html |
| 214 | + |
| 215 | +# Persistent data: only what the user actually modifies at runtime. |
| 216 | +# The application code in /var/www/html is part of the image and is |
| 217 | +# replaced atomically on every `docker pull` + restart — no rsync needed. |
| 218 | +VOLUME /var/www/html/config |
| 219 | +VOLUME /var/www/html/data |
| 220 | +VOLUME /var/www/html/custom_apps |
| 221 | +VOLUME /var/www/html/themes |
| 222 | + |
| 223 | +COPY docker-entrypoint.sh /docker-entrypoint.sh |
| 224 | +COPY cron.sh /cron.sh |
| 225 | +RUN chmod +x /docker-entrypoint.sh /cron.sh |
| 226 | + |
| 227 | +ARG NEXTCLOUD_VERSION |
| 228 | +ARG ENTERPRISE_ARCHIVE_URL |
| 229 | +ENV NEXTCLOUD_VERSION=${NEXTCLOUD_VERSION} |
| 230 | +# Expose whether this is an enterprise build for informational purposes |
| 231 | +ENV NEXTCLOUD_EDITION=${ENTERPRISE_ARCHIVE_URL:+enterprise} |
| 232 | +ENV NEXTCLOUD_EDITION=${NEXTCLOUD_EDITION:-community} |
| 233 | + |
| 234 | +ENTRYPOINT ["/docker-entrypoint.sh"] |
| 235 | +CMD ["apache2-foreground"] |
0 commit comments