From c41d942244ba7313bd1bc6ea9071582d254b7d25 Mon Sep 17 00:00:00 2001 From: Lucilio Correia Date: Mon, 29 Dec 2025 17:27:05 -0300 Subject: [PATCH] add: wordpress stack --- .gitignore | 10 +++ docker-compose.yml | 74 +++++++++++++++++++ docker/nginx/Dockerfile | 10 +++ .../docker-entrypoint.d/10-example-script.sh | 2 + .../global/restrictions.conf.template | 25 +++++++ .../wordpress-regular-site.conf.template | 48 ++++++++++++ docker/wordpress/Dockerfile | 30 ++++++++ .../01-wait-for-db-unpriv.sh | 41 ++++++++++ .../docker-entrypoint.d/05-install-msmtp.sh | 65 ++++++++++++++++ .../10-install-wordpress-unpriv.sh | 29 ++++++++ sample.env | 11 +++ 11 files changed, 345 insertions(+) create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 docker/nginx/Dockerfile create mode 100644 docker/nginx/scripts/docker-entrypoint.d/10-example-script.sh create mode 100644 docker/nginx/templates/global/restrictions.conf.template create mode 100644 docker/nginx/templates/wordpress-regular-site.conf.template create mode 100644 docker/wordpress/Dockerfile create mode 100644 docker/wordpress/scripts/docker-entrypoint.d/01-wait-for-db-unpriv.sh create mode 100644 docker/wordpress/scripts/docker-entrypoint.d/05-install-msmtp.sh create mode 100644 docker/wordpress/scripts/docker-entrypoint.d/10-install-wordpress-unpriv.sh create mode 100644 sample.env diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a76327e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.env +.DS_Store +*.sublime-project +*.sublime-workspace +app/themes +!app/themes/*.theme +!app/themes/*.theme.tgz +app/plugins +!app/themes/*.plugins +!app/themes/*.plugins.tgz diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e53fb48 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,74 @@ +name: wordpress + +services: + + reverse-proxy: + container_name: reverse-proxy + image: hackeamos/httpd + build: + context: './docker/nginx' + depends_on: + - app + volumes: + - webroot:/var/www/html + environment: + WORDPRESS_SITEURL: ${WPSTACK_PROTOCOL}://${WPSTACK_DOMAIN}/${WPSTACK_URLPATH:-} + WORDPRESS_SITE_TYPE: ${WPSTACK_MULTISITE:-regular} + labels: + caddy: ${WPSTACK_DOMAIN} + caddy.reverse_proxy: "reverse-proxy:80" + caddy.tls: "internal" + networks: + - app-network + - proxy-network + + app: + container_name: app + image: hackeamos/wp + build: + context: './docker/wordpress' + volumes: + - webroot:/var/www/html + - ./app/themes:/var/www/html/wp-content/themes + - ./app/plugins:/var/www/html/wp-content/plugins + - ./app/mu-plugins:/var/www/html/wp-content/mu-plugins + environment: + WORDPRESS_DB_HOST: db + WORDPRESS_DB_USER: ${DB_USER} + WORDPRESS_DB_NAME: ${DB_NAME} + WORDPRESS_DB_PASSWORD: ${DB_PASS} + WORDPRESS_SITEURL: ${WPSTACK_PROTOCOL}://${WPSTACK_DOMAIN}/${WPSTACK_URLPATH:-} + WORDPRESS_BLOGNAME: ${WPSTACK_BLOGNAME:-} + WORDPRESS_ADMIN_USER: ${WPSTACK_ADMIN_USER:-admin} + WORDPRESS_ADMIN_EMAIL: ${WPSTACK_ADMIN_EMAIL} + WORDPRESS_MULTISITE: ${WPSTACK_MULTISITE:-} # '' (default), subdomain or path + SMTP_HOST: ${SMTP_HOST:-} + SMTP_PORT: ${SMTP_PORT:-} + SMTP_USER: ${SMTP_USER:-} + SMTP_PASS: ${SMTP_PASS:-} + SMTP_AUTH: ${SMTP_AUTH:-} + SMTP_TLS: ${SMTP_TLS:-} + networks: + - app-network + - proxy-network + + db: + container_name: db + image: mariadb + environment: + MARIADB_RANDOM_ROOT_PASSWORD: true + MARIADB_USER: ${DB_USER} + MARIADB_PASSWORD: ${DB_PASS} + MARIADB_DATABASE: ${DB_NAME} + networks: + - app-network + - proxy-network + +volumes: + webroot: + +networks: + app-network: + proxy-network: + external: true + \ No newline at end of file diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile new file mode 100644 index 0000000..f5bf5fb --- /dev/null +++ b/docker/nginx/Dockerfile @@ -0,0 +1,10 @@ +ARG NGINX_VERSION=stable + +FROM nginx:${NGINX_VERSION}-alpine + +RUN rm -rf /etc/nginx/conf.d/default.conf + +COPY --chmod=755 /scripts/docker-entrypoint.d/* /docker-entrypoint.d +COPY templates /etc/nginx/templates + +EXPOSE 80 \ No newline at end of file diff --git a/docker/nginx/scripts/docker-entrypoint.d/10-example-script.sh b/docker/nginx/scripts/docker-entrypoint.d/10-example-script.sh new file mode 100644 index 0000000..1b0c159 --- /dev/null +++ b/docker/nginx/scripts/docker-entrypoint.d/10-example-script.sh @@ -0,0 +1,2 @@ +#! /bin/sh +echo RUNNING EXAMPLE SCRIPT...; \ No newline at end of file diff --git a/docker/nginx/templates/global/restrictions.conf.template b/docker/nginx/templates/global/restrictions.conf.template new file mode 100644 index 0000000..b434362 --- /dev/null +++ b/docker/nginx/templates/global/restrictions.conf.template @@ -0,0 +1,25 @@ +# Global restrictions configuration file. +# Designed to be included in any server {} block. +location = /favicon.ico { + log_not_found off; + access_log off; +} + +location = /robots.txt { + allow all; + log_not_found off; + access_log off; +} + +# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac). +# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) +location ~ /\. { + deny all; +} + +# Deny access to any files with a .php extension in the uploads directory +# Works in sub-directory installs and also in multisite network +# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) +location ~* /(?:uploads|files)/.*\.php$ { + deny all; +} \ No newline at end of file diff --git a/docker/nginx/templates/wordpress-regular-site.conf.template b/docker/nginx/templates/wordpress-regular-site.conf.template new file mode 100644 index 0000000..477b6e4 --- /dev/null +++ b/docker/nginx/templates/wordpress-regular-site.conf.template @@ -0,0 +1,48 @@ +# WordPress single site rules. +# Designed to be included in any server {} block. +# Upstream to abstract backend connection(s) for php +upstream php { + server unix:/tmp/php-cgi.socket; + server app:9000; +} + +server { + + listen 80; + listen [::]:80; + + server_name _; + root /var/www/html; + index index.php index.html; + + access_log /dev/stdout; + + location = /favicon.ico { + log_not_found off; + access_log off; + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + location / { + # This is cool because no php is touched for static content. + # include the "?$args" part so non-default permalinks doesn't break when using query string + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + include fastcgi.conf; + fastcgi_intercept_errors on; + fastcgi_pass php; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { + expires max; + log_not_found off; + } +} \ No newline at end of file diff --git a/docker/wordpress/Dockerfile b/docker/wordpress/Dockerfile new file mode 100644 index 0000000..a32a256 --- /dev/null +++ b/docker/wordpress/Dockerfile @@ -0,0 +1,30 @@ +ARG WP_VERSION=6 +ARG PHP_VERSION=8.3 + +FROM wordpress:${WP_VERSION}-php${PHP_VERSION}-fpm-alpine + +RUN cp -avf /usr/local/bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.original && \ + sed -i '/^[[:space:]]*exec "$@"[[:space:]]*/d' /usr/local/bin/docker-entrypoint.sh && \ + cat << EOF >> /usr/local/bin/docker-entrypoint.sh +# Run entrypoint hooks +SH_PATH=$(which sh); +if [ -d /docker-entrypoint.d ]; then + for entrypoint in /docker-entrypoint.d/*.sh; do + order=\$(basename \${entrypoint} | grep -oE '^\d+'); + script_user=\$(test -n "\$(basename \${entrypoint} | grep -oE '\-unpriv\.sh')" && echo \${UNPRIV_USER:-\$(stat -c %U /var/www/html)} || echo "root"); + name=\$(basename \${entrypoint} | sed 's/\.sh//' | sed 's/\-unpriv//' | sed 's/^\d\+\-//') + printf "=== %s [#%d] ===\nrunning as '%s'\n\n" \$(echo \${name} | tr [:lower:] [:upper:]) \${order} \${script_user} + su \${script_user} -s \$(which sh) \$entrypoint; + done +fi + +exec "\$@"; +EOF + +RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \ + chmod +x wp-cli.phar && \ + mv wp-cli.phar /usr/local/bin/wp + +# Copy sub scripts and allows execution +COPY ./scripts/docker-entrypoint.d/* /docker-entrypoint.d/ +RUN chmod +x /docker-entrypoint.d/*.sh \ No newline at end of file diff --git a/docker/wordpress/scripts/docker-entrypoint.d/01-wait-for-db-unpriv.sh b/docker/wordpress/scripts/docker-entrypoint.d/01-wait-for-db-unpriv.sh new file mode 100644 index 0000000..14220aa --- /dev/null +++ b/docker/wordpress/scripts/docker-entrypoint.d/01-wait-for-db-unpriv.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env sh +set -eu + +# Maximum number of attempts (can be overridden via MAX_TRIES env var) +MAX_TRIES="${MAX_TRIES:-10}" +# Interval in seconds between attempts (can be overridden via INTERVAL env var) +INTERVAL="${INTERVAL:-5}" + +try=0 + +echo "debug: $0"; + +while [ "$try" -lt "$MAX_TRIES" ]; do + try=$((try + 1)) + echo "trying to reach mysql server (${WORDPRESS_DB_HOST:-${DB_HOST}}:${WORDPRESS_DB_PORT:-${DB_PORT:-3306}}) ${try}/${MAX_TRIES}..."; + + # Test MySQL server readiness using PHP (no mysql client or WP install required) + if php -r ' +error_reporting(0); +$host = getenv("WORDPRESS_DB_HOST") ?: getenv("DB_HOST") ?: "mysql"; +$user = getenv("WORDPRESS_DB_USER") ?: getenv("DB_USER") ?: "root"; +$pass = getenv("WORDPRESS_DB_PASSWORD") ?: getenv("DB_PASSWORD") ?: ""; +$port = getenv("WORDPRESS_DB_PORT") ?: getenv("DB_PORT") ?: 3306; + +$mysqli = @new mysqli($host, $user, $pass, "", (int) $port); + +if ($mysqli && !$mysqli->connect_errno) { + exit(0); // connection OK +} +exit(1); // connection failed +'; then + #echo "MySQL is ready, continuing..." + exit 0 + fi + + sleep "$INTERVAL" +done + +DURATION=$((MAX_TRIES * INTERVAL)) +echo "MySQL was not ready after ${DURATION}s" >&2 +exit 1 diff --git a/docker/wordpress/scripts/docker-entrypoint.d/05-install-msmtp.sh b/docker/wordpress/scripts/docker-entrypoint.d/05-install-msmtp.sh new file mode 100644 index 0000000..f402bfe --- /dev/null +++ b/docker/wordpress/scripts/docker-entrypoint.d/05-install-msmtp.sh @@ -0,0 +1,65 @@ +# Instals msmtp if a SMTP_HOST var was configured +if test -n "${SMTP_HOST}"; then + + apk add --no-cache msmtp; + + if test -z "${SMTP_PORT:-}"; then + if test "${SMTP_TLS:-on}" = "off"; then + SMTP_PORT=25; + elif test "${SMTP_TLS:-on}" = "starttls"; then + SMTP_PORT=587; + else + SMTP_PORT=465; + fi + fi + + echo "**** SMTP_AUTH=${SMTP_AUTH:-}"; + + if test "${SMTP_AUTH:-on}" = "off"; then + SMTP_TLS="off"; + fi + + cat > /etc/msmtprc <> /etc/msmtprc; + fi + + if test -n "${SMTP_USER}"; then + echo "user ${SMTP_USER}" >> /etc/msmtprc; + fi + + if test -n "${SMTP_PASSWORD}"; then + echo password ${SMTP_PASSWORD} >> /etc/msmtprc; + fi + + cat > /usr/local/etc/php/conf.d/99-mail.ini <&2 echo "FAILED: \$${varname} variable must be defined to proceed" && exit 2; + fi +done; + +pwd + +if which wp && ! wp core is-installed; then + echo "Installing WordPress via WP-CLI..." + wp core install --url="$WORDPRESS_SITEURL" \ + --title="$WORDPRESS_BLOGNAME" \ + --admin_user="$WORDPRESS_ADMIN_USER" \ + --admin_email="$WORDPRESS_ADMIN_EMAIL" \ + --skip-email || exit 1 + echo "WordPress installed successfully" +fi + +if test "$(wp theme list --format=count)" = "0"; then + wp theme install twentytwentyfive --force --activate; +fi \ No newline at end of file diff --git a/sample.env b/sample.env new file mode 100644 index 0000000..e31b927 --- /dev/null +++ b/sample.env @@ -0,0 +1,11 @@ +DB_HOST=db +DB_USER=wordpress +DB_NAME=wordpress +DB_PASS=mysecretpassword +WPSTACK_ADMIN_EMAIL=contato@example.com # use a valid email +WPSTACK_PROTOCOL=https +WPSTACK_DOMAIN=localhost +WPSTACK_BLOGNAME=My WP Site +SMTP_HOST=mailpit +SMTP_PORT=1025 +SMTP_AUTH=off \ No newline at end of file