├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── 00-deployment-verification.yml ├── .env ├── wordpress-restore-database.sh ├── wordpress-restore-application-data.sh ├── .gitignore ├── README.md └── wordpress-traefik-letsencrypt-docker-compose.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: heyvaldemar 2 | patreon: heyvaldemar 3 | ko_fi: heyvaldemar 4 | custom: ['paypal.com/paypalme/heyValdemarCOM', 'buymeacoffee.com/heyValdemar', 'ko-fi.com/heyValdemar'] 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Traefik Variables 2 | TRAEFIK_IMAGE_TAG=traefik:3.2 3 | # Set the log level (DEBUG, INFO, WARN, ERROR) 4 | TRAEFIK_LOG_LEVEL=WARN 5 | # The email address used by Let's Encrypt for renewal notices 6 | TRAEFIK_ACME_EMAIL=admin@example.com 7 | # The hostname used to access the Traefik dashboard and to configure domain-specific rules 8 | TRAEFIK_HOSTNAME=traefik.www.heyvaldemarwp.net 9 | # Basic Authentication for Traefik Dashboard 10 | # Username: traefikadmin 11 | # Passwords must be encoded using MD5, SHA1, or BCrypt https://hostingcanada.org/htpasswd-generator/ 12 | TRAEFIK_BASIC_AUTH=traefikadmin:$$2y$$10$$sMzJfirKC75x/hVpiINeZOiSm.Jkity9cn4KwNkRvO7hSQVFc5FLO 13 | 14 | # WordPress Variables 15 | WORDPRESS_MARIADB_IMAGE_TAG=mariadb:11.1 16 | WORDPRESS_IMAGE_TAG=bitnami/wordpress:latest 17 | WORDPRESS_DB_NAME=wordpressdb 18 | WORDPRESS_DB_USER=wordpressdbbuser 19 | WORDPRESS_DB_PASSWORD=DH8MowfpuwGyBfNcnafE 20 | WORDPRESS_DB_ADMIN_PASSWORD=TMfmVC4uJLyXVrjoRfGP 21 | WORDPRESS_TABLE_PREFIX=wpapp_ 22 | WORDPRESS_BLOG_NAME=WordPress 23 | WORDPRESS_ADMIN_NAME=WordPress 24 | WORDPRESS_ADMIN_LASTNAME=Admin 25 | WORDPRESS_ADMIN_USERNAME=wordpressadmin 26 | WORDPRESS_ADMIN_PASSWORD=PqspCAOJrqh78i725Te8 27 | WORDPRESS_ADMIN_EMAIL=wordpressadmin@heyvaldemar.net 28 | WORDPRESS_HOSTNAME=www.heyvaldemarwp.net 29 | WORDPRESS_ROOT_DOMAIN=heyvaldemarwp.net 30 | WORDPRESS_SMTP_ADDRESS=smtp-relay.gmail.com 31 | WORDPRESS_SMTP_PORT=587 32 | WORDPRESS_SMTP_USER_NAME=wordpress@heyvaldemar.net 33 | WORDPRESS_SMTP_PASSWORD=gi8oFBiXLZkWuGobstus 34 | 35 | # Backup Variables 36 | BACKUP_INIT_SLEEP=30m 37 | BACKUP_INTERVAL=24h 38 | MARIADB_BACKUP_PRUNE_DAYS=30 39 | DATA_BACKUP_PRUNE_DAYS=30 40 | MARIADB_BACKUPS_PATH=/srv/wordpress-mariadb/backups 41 | DATA_BACKUPS_PATH=/srv/wordpress-application-data/backups 42 | DATA_PATH=/bitnami/wordpress 43 | DATA_BACKUP_PATH=/bitnami/wordpress 44 | MARIADB_BACKUP_NAME=wordpress-mariadb-backup 45 | DATA_BACKUP_NAME=wordpress-application-data-backup 46 | -------------------------------------------------------------------------------- /.github/workflows/00-deployment-verification.yml: -------------------------------------------------------------------------------- 1 | name: Deployment Verification 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | deploy-and-test: 13 | runs-on: ubuntu-latest 14 | 15 | env: 16 | NETWORK_ONE: wordpress-network 17 | NETWORK_TWO: traefik-network 18 | DOCKER_COMPOSE_FILE: wordpress-traefik-letsencrypt-docker-compose.yml 19 | APP_HOSTNAME: www.heyvaldemarwp.net 20 | APP_TRAEFIK_HOSTNAME: traefik.www.heyvaldemarwp.net 21 | COMPOSE_PROJECT_NAME: wordpress 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v6 26 | 27 | - name: Create necessary Docker networks 28 | run: | 29 | docker network create $NETWORK_ONE || true 30 | docker network create $NETWORK_TWO || true 31 | 32 | - name: Start up services using Docker Compose 33 | run: docker compose -f $DOCKER_COMPOSE_FILE -p $COMPOSE_PROJECT_NAME up -d 34 | 35 | - name: Modify /etc/hosts for internal routing 36 | run: | 37 | echo "127.0.0.1 $APP_HOSTNAME" | sudo tee -a /etc/hosts 38 | echo "127.0.0.1 $APP_TRAEFIK_HOSTNAME" | sudo tee -a /etc/hosts 39 | 40 | - name: Print Docker Compose services status 41 | run: docker ps 42 | 43 | - name: Wait for the application to be ready via Traefik 44 | run: | 45 | echo "Checking the routing and availability of the application via Traefik..." 46 | timeout 5m bash -c 'while ! curl -fsSLk "https://$APP_HOSTNAME"; do \ 47 | echo "Waiting for the application to be ready..."; \ 48 | sleep 10; \ 49 | done' 50 | 51 | - name: Wait for the Traefik dashboard to be ready 52 | run: | 53 | echo "Checking the routing and availability of the Traefik dashboard..." 54 | timeout 5m bash -c 'while ! curl -fsSLk --write-out "%{http_code}" --output /dev/null "https://$APP_TRAEFIK_HOSTNAME" | grep -E "200|401"; do \ 55 | echo "Waiting for the application to be ready..."; \ 56 | sleep 10; \ 57 | done' 58 | 59 | - name: Inspect Network Configuration 60 | run: | 61 | docker network inspect $NETWORK_ONE 62 | docker network inspect $NETWORK_TWO 63 | 64 | - name: Show container logs on failure 65 | if: failure() 66 | run: docker compose -f $DOCKER_COMPOSE_FILE -p $COMPOSE_PROJECT_NAME logs 67 | 68 | - name: Shutdown Docker Compose services 69 | if: always() 70 | run: docker compose -f $DOCKER_COMPOSE_FILE -p $COMPOSE_PROJECT_NAME down 71 | -------------------------------------------------------------------------------- /wordpress-restore-database.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -Eeuo pipefail 3 | trap 'echo "[ERR] Line $LINENO"; exit 1' ERR 4 | 5 | # --- Configuration --- 6 | PROJECT=${PROJECT:-wordpress} # Docker Compose project name 7 | SVC_DB=${SVC_DB:-mariadb} # DB service name in docker-compose 8 | SVC_WP=${SVC_WP:-wordpress} # WordPress service name 9 | SVC_BKP=${SVC_BKP:-backups} # Backups service name 10 | 11 | DB_BACKUP_DIR=${DB_BACKUP_DIR:-/srv/wordpress-mariadb/backups} 12 | 13 | # --- Get container IDs (with fallbacks) --- 14 | CID_DB=$(docker compose -p "$PROJECT" ps -q "$SVC_DB" || true) 15 | CID_WP=$(docker compose -p "$PROJECT" ps -q "$SVC_WP" || true) 16 | CID_BKP=$(docker compose -p "$PROJECT" ps -q "$SVC_BKP" || true) 17 | 18 | [ -n "$CID_DB" ] || CID_DB=$(docker ps -qf "name=${PROJECT}-${SVC_DB}") 19 | [ -n "$CID_WP" ] || CID_WP=$(docker ps -qf "name=${PROJECT}-${SVC_WP}") 20 | [ -n "$CID_BKP" ] || CID_BKP=$(docker ps -qf "name=${PROJECT}-${SVC_BKP}") 21 | 22 | [ -n "$CID_DB" ] || { echo "[ERR] DB container not found"; exit 1; } 23 | [ -n "$CID_WP" ] || { echo "[ERR] WP container not found"; exit 1; } 24 | [ -n "$CID_BKP" ] || { echo "[ERR] Backups container not found"; exit 1; } 25 | 26 | # --- Get DB credentials from the backup container env --- 27 | DB_NAME=$(docker exec "$CID_BKP" printenv WORDPRESS_DB_NAME) 28 | DB_USER=$(docker exec "$CID_BKP" printenv WORDPRESS_DB_USER) 29 | DB_PASS=$(docker exec "$CID_BKP" printenv WORDPRESS_DB_PASSWORD) 30 | 31 | # --- Show available backups --- 32 | echo "--> Available DB backups:" 33 | docker exec "$CID_BKP" sh -lc "ls -1 ${DB_BACKUP_DIR}/*.gz 2>/dev/null || true" 34 | 35 | # --- Ask user which backup to restore --- 36 | read -r -p "--> Enter backup filename (e.g. wordpress-mariadb-backup-YYYY-MM-DD_hh-mm.gz): " SELECTED 37 | [ -n "$SELECTED" ] || { echo "[ERR] empty filename"; exit 1; } 38 | 39 | # --- Check file exists --- 40 | docker exec "$CID_BKP" sh -lc "test -f '${DB_BACKUP_DIR}/${SELECTED}'" || { echo "[ERR] file not found"; exit 1; } 41 | 42 | # --- Put site into maintenance mode (best effort) --- 43 | docker exec "$CID_WP" sh -lc "command -v wp >/dev/null && wp maintenance-mode activate || true" || true 44 | 45 | # --- Stop WP service before restore --- 46 | docker compose -p "$PROJECT" stop "$SVC_WP" 47 | 48 | # --- Restore database (session-level FK off; no SUPER required) --- 49 | docker exec -e MYSQL_PWD="$DB_PASS" "$CID_BKP" sh -lc " 50 | mariadb -h $SVC_DB -u $DB_USER -e \"DROP DATABASE IF EXISTS \\\`$DB_NAME\\\`; CREATE DATABASE \\\`$DB_NAME\\\`;\" 51 | ( echo 'SET FOREIGN_KEY_CHECKS=0;' 52 | gunzip -c '${DB_BACKUP_DIR}/${SELECTED}' 53 | echo 'SET FOREIGN_KEY_CHECKS=1;' ) \ 54 | | mariadb -h $SVC_DB -u $DB_USER $DB_NAME --force --max-allowed-packet=256M 55 | " 56 | 57 | # --- Start WP again --- 58 | docker compose -p "$PROJECT" start "$SVC_WP" 59 | 60 | # --- Disable maintenance mode --- 61 | docker exec "$CID_WP" sh -lc "command -v wp >/dev/null && wp maintenance-mode deactivate || true" || true 62 | 63 | echo "--> DB restore completed successfully." 64 | -------------------------------------------------------------------------------- /wordpress-restore-application-data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -Eeuo pipefail 3 | trap 'echo "[ERR] Line $LINENO"; exit 1' ERR 4 | 5 | # --- Configuration (override via env if needed) --- 6 | PROJECT=${PROJECT:-wordpress} # Docker Compose project name 7 | SVC_WP=${SVC_WP:-wordpress} # WordPress service name in docker-compose 8 | SVC_BKP=${SVC_BKP:-backups} # Backups service name 9 | 10 | APP_BACKUP_DIR=${APP_BACKUP_DIR:-/srv/wordpress-application-data/backups} 11 | 12 | # We restore the entire /bitnami/wordpress directory 13 | RESTORE_PATH=${RESTORE_PATH:-/bitnami/wordpress/} 14 | 15 | # Bitnami expects wp-config.php to be available in /opt/bitnami/wordpress 16 | WP_CONFIG_SRC=${WP_CONFIG_SRC:-/bitnami/wordpress/wp-config.php} 17 | WP_CONFIG_DST=${WP_CONFIG_DST:-/opt/bitnami/wordpress/wp-config.php} 18 | 19 | # --- Helper: find container by docker-compose labels --- 20 | find_by_labels () { 21 | local project="$1" service="$2" 22 | docker ps -aq \ 23 | --filter "label=com.docker.compose.project=${project}" \ 24 | --filter "label=com.docker.compose.service=${service}" 25 | } 26 | 27 | # --- Resolve container IDs --- 28 | CID_WP=$(find_by_labels "$PROJECT" "$SVC_WP" || true) 29 | CID_BKP=$(find_by_labels "$PROJECT" "$SVC_BKP" || true) 30 | 31 | [ -n "$CID_WP" ] || CID_WP=$(docker compose -p "$PROJECT" ps -q --all "$SVC_WP" || true) 32 | [ -n "$CID_BKP" ] || CID_BKP=$(docker compose -p "$PROJECT" ps -q --all "$SVC_BKP" || true) 33 | 34 | [ -n "$CID_WP" ] || CID_WP=$(docker ps -aqf "name=${PROJECT}-${SVC_WP}") 35 | [ -n "$CID_BKP" ] || CID_BKP=$(docker ps -aqf "name=${PROJECT}-${SVC_BKP}") 36 | 37 | echo "[DBG] PROJECT=${PROJECT} SVC_WP=${SVC_WP} SVC_BKP=${SVC_BKP}" 38 | echo "[DBG] CID_WP=${CID_WP:-} CID_BKP=${CID_BKP:-}" 39 | 40 | # --- Guards --- 41 | [ -n "$CID_WP" ] || { echo "[ERR] WP container not found"; exit 1; } 42 | [ -n "$CID_BKP" ] || { echo "[ERR] Backups container not found"; exit 1; } 43 | 44 | # --- Check restore path exists inside backups container --- 45 | docker exec "$CID_BKP" sh -lc "test -d '${RESTORE_PATH}'" \ 46 | || { echo "[ERR] RESTORE_PATH does not exist inside backups container: ${RESTORE_PATH}"; exit 1; } 47 | 48 | # --- List available backups --- 49 | echo "--> Available application-data backups (full /bitnami/wordpress):" 50 | docker exec "$CID_BKP" sh -lc "ls -1 ${APP_BACKUP_DIR}/*.tar.gz 2>/dev/null || true" 51 | 52 | # --- Prompt user for archive filename --- 53 | read -r -p "--> Enter backup filename (e.g. wordpress-application-data-backup-YYYY-MM-DD_hh-mm.tar.gz): " SELECTED 54 | [ -n "$SELECTED" ] || { echo "[ERR] empty filename"; exit 1; } 55 | 56 | # --- Verify backup file exists --- 57 | docker exec "$CID_BKP" sh -lc "test -f '${APP_BACKUP_DIR}/${SELECTED}'" \ 58 | || { echo "[ERR] file not found: ${APP_BACKUP_DIR}/${SELECTED}"; exit 1; } 59 | 60 | # --- Ensure RESTORE_PATH is correct --- 61 | case "$RESTORE_PATH" in 62 | */bitnami/wordpress/ ) : ;; 63 | * ) echo "[ERR] RESTORE_PATH must be /bitnami/wordpress/: ${RESTORE_PATH}"; exit 1;; 64 | esac 65 | 66 | # --- Best-effort maintenance mode (requires wp-cli inside WP container) --- 67 | docker exec "$CID_WP" sh -lc "command -v wp >/dev/null && wp maintenance-mode activate || true" || true 68 | 69 | # --- Stop WP container before restore --- 70 | docker compose -p "$PROJECT" stop "$SVC_WP" || true 71 | 72 | # --- Perform restore inside backups container --- 73 | docker exec "$CID_BKP" sh -lc " 74 | set -euo noglob 75 | # Safety checks 76 | test -d '${RESTORE_PATH}' && [ '${RESTORE_PATH}' != '/' ] && [ -n '${RESTORE_PATH}' ] 77 | rm -rf '${RESTORE_PATH%/}'/* 78 | 79 | # Archive contains bitnami/wordpress/... -> extract to / 80 | tar -zxpf '${APP_BACKUP_DIR}/${SELECTED}' -C / 81 | 82 | # Ensure no stray wp-config.php inside wp-content (defense-in-depth) 83 | rm -f '${RESTORE_PATH}/wp-content/wp-config.php' 2>/dev/null || true 84 | " 85 | 86 | # --- Fix permissions (match Bitnami expectations) --- 87 | # wp-content should be owned by daemon:daemon, but wp-config.php must be root:root 440 88 | docker exec "$CID_BKP" sh -lc " 89 | if [ -d '${RESTORE_PATH%/}/wp-content' ]; then 90 | chown -R daemon:daemon '${RESTORE_PATH%/}/wp-content' 91 | fi 92 | if [ -f '${WP_CONFIG_SRC}' ]; then 93 | chown root:root '${WP_CONFIG_SRC}' 94 | chmod 440 '${WP_CONFIG_SRC}' 95 | fi 96 | " 97 | 98 | # --- Start WP container again --- 99 | docker compose -p "$PROJECT" start "$SVC_WP" || true 100 | 101 | # --- Ensure wp-config.php symlink is in place --- 102 | echo "--> Ensuring wp-config symlink inside WP container..." 103 | ATTEMPTS=30 104 | SLEEP_SECS=2 105 | for i in $(seq 1 $ATTEMPTS); do 106 | if docker exec "$CID_WP" sh -lc "[ -f '${WP_CONFIG_SRC}' ] && ln -sf '${WP_CONFIG_SRC}' '${WP_CONFIG_DST}' && ls -l '${WP_CONFIG_DST}'" >/dev/null 2>&1; then 107 | echo "--> Symlink OK: ${WP_CONFIG_DST} -> ${WP_CONFIG_SRC}" 108 | break 109 | fi 110 | echo "[DBG] WP not ready yet, retry ${i}/${ATTEMPTS}..." 111 | sleep "$SLEEP_SECS" 112 | docker compose -p "$PROJECT" start "$SVC_WP" >/dev/null 2>&1 || true 113 | done 114 | 115 | # --- Disable maintenance mode (best effort) --- 116 | docker exec "$CID_WP" sh -lc "command -v wp >/dev/null && wp maintenance-mode deactivate || true" || true 117 | 118 | # --- Final checks --- 119 | docker exec "$CID_WP" sh -lc " 120 | echo '--> Post-checks:' 121 | ls -ld /bitnami/wordpress /bitnami/wordpress/wp-content || true 122 | [ -e '${WP_CONFIG_SRC}' ] && stat -c 'OK: %U:%G %a ${WP_CONFIG_SRC}' '${WP_CONFIG_SRC}' || echo 'WARN: wp-config.php missing at ${WP_CONFIG_SRC}' 123 | [ -e '${WP_CONFIG_DST}' ] && echo 'OK: wp-config.php link exists at ${WP_CONFIG_DST}' || echo 'WARN: wp-config.php link missing at ${WP_CONFIG_DST}' 124 | " || true 125 | 126 | echo "--> Application data restore (full /bitnami/wordpress) completed successfully." 127 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/git,macos,xcode,jekyll,packer,ansible,vagrant,windows,notepadpp,terraform,powershell,terragrunt,sublimetext,ansibletower,visualstudiocode,linux 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=git,macos,xcode,jekyll,packer,ansible,vagrant,windows,notepadpp,terraform,powershell,terragrunt,sublimetext,ansibletower,visualstudiocode,linux 3 | 4 | ### Ansible ### 5 | *.retry 6 | 7 | ### AnsibleTower ### 8 | # Ansible runtime and backups 9 | *.original 10 | *.tmp 11 | *.bkp 12 | *.*~ 13 | 14 | # Tower runtime roles 15 | roles/** 16 | !roles/requirements.yml 17 | 18 | # Avoid plain-text passwords 19 | *pwd* 20 | *pass* 21 | *password* 22 | *.txt 23 | 24 | # Exclude all binaries 25 | *.bin 26 | *.jar 27 | *.tar 28 | *.zip 29 | *.gzip 30 | *.tgz 31 | 32 | 33 | ### Git ### 34 | # Created by git for backups. To disable backups in Git: 35 | # $ git config --global mergetool.keepBackup false 36 | *.orig 37 | 38 | # Created by git when using merge tools for conflicts 39 | *.BACKUP.* 40 | *.BASE.* 41 | *.LOCAL.* 42 | *.REMOTE.* 43 | *_BACKUP_*.txt 44 | *_BASE_*.txt 45 | *_LOCAL_*.txt 46 | *_REMOTE_*.txt 47 | 48 | ### Jekyll ### 49 | _site/ 50 | .sass-cache/ 51 | .jekyll-cache/ 52 | .jekyll-metadata 53 | # Ignore folders generated by Bundler 54 | .bundle/ 55 | vendor/ 56 | 57 | ### Linux ### 58 | *~ 59 | 60 | # temporary files which can be created if a process still has a handle open of a deleted file 61 | .fuse_hidden* 62 | 63 | # KDE directory preferences 64 | .directory 65 | 66 | # Linux trash folder which might appear on any partition or disk 67 | .Trash-* 68 | 69 | # .nfs files are created when an open file is removed but is still being accessed 70 | .nfs* 71 | 72 | ### macOS ### 73 | # General 74 | .DS_Store 75 | .AppleDouble 76 | .LSOverride 77 | 78 | # Icon must end with two \r 79 | Icon 80 | 81 | 82 | # Thumbnails 83 | ._* 84 | 85 | # Files that might appear in the root of a volume 86 | .DocumentRevisions-V100 87 | .fseventsd 88 | .Spotlight-V100 89 | .TemporaryItems 90 | .Trashes 91 | .VolumeIcon.icns 92 | .com.apple.timemachine.donotpresent 93 | 94 | # Directories potentially created on remote AFP share 95 | .AppleDB 96 | .AppleDesktop 97 | Network Trash Folder 98 | Temporary Items 99 | .apdisk 100 | 101 | ### macOS Patch ### 102 | # iCloud generated files 103 | *.icloud 104 | 105 | ### NotepadPP ### 106 | # Notepad++ backups # 107 | *.bak 108 | 109 | ### Packer ### 110 | # Cache objects 111 | packer_cache/ 112 | 113 | # Crash log 114 | crash.log 115 | 116 | # https://www.packer.io/guides/hcl/variables 117 | # Exclude all .pkrvars.hcl files, which are likely to contain sensitive data, 118 | # such as password, private keys, and other secrets. These should not be part of 119 | # version control as they are data points which are potentially sensitive and 120 | # subject to change depending on the environment. 121 | # 122 | *.pkrvars.hcl 123 | 124 | # For built boxes 125 | *.box 126 | 127 | ### Packer Patch ### 128 | # ignore temporary output files 129 | output-*/ 130 | 131 | ### PowerShell ### 132 | # Exclude packaged modules 133 | 134 | # Exclude .NET assemblies from source 135 | *.dll 136 | 137 | ### SublimeText ### 138 | # Cache files for Sublime Text 139 | *.tmlanguage.cache 140 | *.tmPreferences.cache 141 | *.stTheme.cache 142 | 143 | # Workspace files are user-specific 144 | *.sublime-workspace 145 | 146 | # Project files should be checked into the repository, unless a significant 147 | # proportion of contributors will probably not be using Sublime Text 148 | # *.sublime-project 149 | 150 | # SFTP configuration file 151 | sftp-config.json 152 | sftp-config-alt*.json 153 | 154 | # Package control specific files 155 | Package Control.last-run 156 | Package Control.ca-list 157 | Package Control.ca-bundle 158 | Package Control.system-ca-bundle 159 | Package Control.cache/ 160 | Package Control.ca-certs/ 161 | Package Control.merged-ca-bundle 162 | Package Control.user-ca-bundle 163 | oscrypto-ca-bundle.crt 164 | bh_unicode_properties.cache 165 | 166 | # Sublime-github package stores a github token in this file 167 | # https://packagecontrol.io/packages/sublime-github 168 | GitHub.sublime-settings 169 | 170 | ### Terraform ### 171 | # Local .terraform directories 172 | **/.terraform/* 173 | 174 | # .tfstate files 175 | *.tfstate 176 | *.tfstate.* 177 | 178 | # Crash log files 179 | crash.*.log 180 | 181 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 182 | # password, private keys, and other secrets. These should not be part of version 183 | # control as they are data points which are potentially sensitive and subject 184 | # to change depending on the environment. 185 | *.tfvars 186 | *.tfvars.json 187 | 188 | # Ignore override files as they are usually used to override resources locally and so 189 | # are not checked in 190 | override.tf 191 | override.tf.json 192 | *_override.tf 193 | *_override.tf.json 194 | 195 | # Include override files you do wish to add to version control using negated pattern 196 | # !example_override.tf 197 | 198 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 199 | # example: *tfplan* 200 | 201 | # Ignore CLI configuration files 202 | .terraformrc 203 | terraform.rc 204 | 205 | ### Terragrunt ### 206 | # terragrunt cache directories 207 | **/.terragrunt-cache/* 208 | 209 | # Terragrunt debug output file (when using `--terragrunt-debug` option) 210 | # See: https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-debug 211 | terragrunt-debug.tfvars.json 212 | 213 | ### Vagrant ### 214 | # General 215 | .vagrant/ 216 | 217 | # Log files (if you are creating logs in debug mode, uncomment this) 218 | # *.log 219 | 220 | ### Vagrant Patch ### 221 | 222 | ### VisualStudioCode ### 223 | .vscode/* 224 | !.vscode/settings.json 225 | !.vscode/tasks.json 226 | !.vscode/launch.json 227 | !.vscode/extensions.json 228 | !.vscode/*.code-snippets 229 | 230 | # Local History for Visual Studio Code 231 | .history/ 232 | 233 | # Built Visual Studio Code Extensions 234 | *.vsix 235 | 236 | ### VisualStudioCode Patch ### 237 | # Ignore all local history of files 238 | .history 239 | .ionide 240 | 241 | ### Windows ### 242 | # Windows thumbnail cache files 243 | Thumbs.db 244 | Thumbs.db:encryptable 245 | ehthumbs.db 246 | ehthumbs_vista.db 247 | 248 | # Dump file 249 | *.stackdump 250 | 251 | # Folder config file 252 | [Dd]esktop.ini 253 | 254 | # Recycle Bin used on file shares 255 | $RECYCLE.BIN/ 256 | 257 | # Windows Installer files 258 | *.cab 259 | *.msi 260 | *.msix 261 | *.msm 262 | *.msp 263 | 264 | # Windows shortcuts 265 | *.lnk 266 | 267 | ### Xcode ### 268 | ## User settings 269 | xcuserdata/ 270 | 271 | ## Xcode 8 and earlier 272 | *.xcscmblueprint 273 | *.xccheckout 274 | 275 | ### Xcode Patch ### 276 | *.xcodeproj/* 277 | !*.xcodeproj/project.pbxproj 278 | !*.xcodeproj/xcshareddata/ 279 | !*.xcodeproj/project.xcworkspace/ 280 | !*.xcworkspace/contents.xcworkspacedata 281 | /*.gcno 282 | **/xcshareddata/WorkspaceSettings.xcsettings 283 | 284 | # End of https://www.toptal.com/developers/gitignore/api/git,macos,xcode,jekyll,packer,ansible,vagrant,windows,notepadpp,terraform,powershell,terragrunt,sublimetext,ansibletower,visualstudiocode,linux -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPress with Let's Encrypt Using Docker Compose 2 | 3 | [![Deployment Verification](https://github.com/heyvaldemar/wordpress-traefik-letsencrypt-docker-compose/actions/workflows/00-deployment-verification.yml/badge.svg)](https://github.com/heyvaldemar/wordpress-traefik-letsencrypt-docker-compose/actions) 4 | 5 | The badge displayed on my repository indicates the status of the deployment verification workflow as executed on the latest commit to the main branch. 6 | 7 | **Passing**: This means the most recent commit has successfully passed all deployment checks, confirming that the Docker Compose setup functions correctly as designed. 8 | 9 | 📙 The complete installation guide is available on my [website](https://www.heyvaldemar.com/install-wordpress-using-docker-compose/). 10 | 11 | ❗ Change variables in the `.env` to meet your requirements. 12 | 13 | 💡 Note that the `.env` file should be in the same directory as `wordpress-traefik-letsencrypt-docker-compose.yml`. 14 | 15 | Create networks for your services before deploying the configuration using the commands: 16 | 17 | `docker network create traefik-network` 18 | 19 | `docker network create wordpress-network` 20 | 21 | Deploy WordPress using Docker Compose: 22 | 23 | `docker compose -f wordpress-traefik-letsencrypt-docker-compose.yml -p wordpress up -d` 24 | 25 | ## Backups 26 | 27 | The `backups` container in the configuration is responsible for the following: 28 | 29 | 1. **Database Backup**: Creates compressed backups of the MariaDB database using pg_dump. 30 | Customizable backup path, filename pattern, and schedule through variables like `MARIADB_BACKUPS_PATH`, `MARIADB_BACKUP_NAME`, and `BACKUP_INTERVAL`. 31 | 32 | 2. **Application Data Backup**: Compresses and stores backups of the application data on the same schedule. Controlled via variables such as `DATA_BACKUPS_PATH`, `DATA_BACKUP_NAME`, and `BACKUP_INTERVAL`. 33 | 34 | 3. **Backup Pruning**: Periodically removes backups exceeding a specified age to manage storage. Customizable pruning schedule and age threshold with `MARIADB_BACKUP_PRUNE_DAYS` and `DATA_BACKUP_PRUNE_DAYS`. 35 | 36 | By utilizing this container, consistent and automated backups of the essential components of your instance are ensured. Moreover, efficient management of backup storage and tailored backup routines can be achieved through easy and flexible configuration using environment variables. 37 | 38 | ## wordpress-restore-database.sh Description 39 | 40 | This script facilitates the restoration of a database backup: 41 | 42 | 1. **Identify Containers**: It first identifies the service and backups containers by name, finding the appropriate container IDs. 43 | 44 | 2. **List Backups**: Displays all available database backups located at the specified backup path. 45 | 46 | 3. **Select Backup**: Prompts the user to copy and paste the desired backup name from the list to restore the database. 47 | 48 | 4. **Stop Service**: Temporarily stops the service to ensure data consistency during restoration. 49 | 50 | 5. **Restore Database**: Executes a sequence of commands to drop the current database, create a new one, and restore it from the selected compressed backup file. 51 | 52 | 6. **Start Service**: Restarts the service after the restoration is completed. 53 | 54 | To make the `wordpress-restore-database.shh` script executable, run the following command: 55 | 56 | `chmod +x wordpress-restore-database.sh` 57 | 58 | Usage of this script ensures a controlled and guided process to restore the database from an existing backup. 59 | 60 | ## wordpress-restore-application-data.sh Description 61 | 62 | This script is designed to restore the application data: 63 | 64 | 1. **Identify Containers**: Similarly to the database restore script, it identifies the service and backups containers by name. 65 | 66 | 2. **List Application Data Backups**: Displays all available application data backups at the specified backup path. 67 | 68 | 3. **Select Backup**: Asks the user to copy and paste the desired backup name for application data restoration. 69 | 70 | 4. **Stop Service**: Stops the service to prevent any conflicts during the restore process. 71 | 72 | 5. **Restore Application Data**: Removes the current application data and then extracts the selected backup to the appropriate application data path. 73 | 74 | 6. **Start Service**: Restarts the service after the application data has been successfully restored. 75 | 76 | To make the `wordpress-restore-application-data.sh` script executable, run the following command: 77 | 78 | `chmod +x wordpress-restore-application-data.sh` 79 | 80 | By utilizing this script, you can efficiently restore application data from an existing backup while ensuring proper coordination with the running service. 81 | 82 | ## Author 83 | 84 | hey everyone, 85 | 86 | 💾 I’ve been in the IT game for over 20 years, cutting my teeth with some big names like [IBM](https://www.linkedin.com/in/heyvaldemar/), [Thales](https://www.linkedin.com/in/heyvaldemar/), and [Amazon](https://www.linkedin.com/in/heyvaldemar/). These days, I wear the hat of a DevOps Consultant and Team Lead, but what really gets me going is Docker and container technology - I’m kind of obsessed! 87 | 88 | 💛 I have my own IT [blog](https://www.heyvaldemar.com/), where I’ve built a [community](https://discord.gg/AJQGCCBcqf) of DevOps enthusiasts who share my love for all things Docker, containers, and IT technologies in general. And to make sure everyone can jump on this awesome DevOps train, I write super detailed guides (seriously, they’re foolproof!) that help even newbies deploy and manage complex IT solutions. 89 | 90 | 🚀 My dream is to empower every single person in the DevOps community to squeeze every last drop of potential out of Docker and container tech. 91 | 92 | 🐳 As a [Docker Captain](https://www.docker.com/captains/vladimir-mikhalev/), I’m stoked to share my knowledge, experiences, and a good dose of passion for the tech. My aim is to encourage learning, innovation, and growth, and to inspire the next generation of IT whizz-kids to push Docker and container tech to its limits. 93 | 94 | Let’s do this together! 95 | 96 | ## My 2D Portfolio 97 | 98 | 🕹️ Click into [sre.gg](https://www.sre.gg/) — my virtual space is a 2D pixel-art portfolio inviting you to interact with elements that encapsulate the milestones of my DevOps career. 99 | 100 | ## My Courses 101 | 102 | 🎓 Dive into my [comprehensive IT courses](https://www.heyvaldemar.com/courses/) designed for enthusiasts and professionals alike. Whether you're looking to master Docker, conquer Kubernetes, or advance your DevOps skills, my courses provide a structured pathway to enhancing your technical prowess. 103 | 104 | 🔑 [Each course](https://www.udemy.com/user/heyvaldemar/) is built from the ground up with real-world scenarios in mind, ensuring that you gain practical knowledge and hands-on experience. From beginners to seasoned professionals, there's something here for everyone to elevate their IT skills. 105 | 106 | ## My Services 107 | 108 | 💼 Take a look at my [service catalog](https://www.heyvaldemar.com/services/) and find out how we can make your technological life better. Whether it's increasing the efficiency of your IT infrastructure, advancing your career, or expanding your technological horizons — I'm here to help you achieve your goals. From DevOps transformations to building gaming computers — let's make your technology unparalleled! 109 | 110 | ## Patreon Exclusives 111 | 112 | 🏆 Join my [Patreon](https://www.patreon.com/heyvaldemar) and dive deep into the world of Docker and DevOps with exclusive content tailored for IT enthusiasts and professionals. As your experienced guide, I offer a range of membership tiers designed to suit everyone from newbies to IT experts. 113 | 114 | ## My Recommendations 115 | 116 | 📕 Check out my collection of [essential DevOps books](https://kit.co/heyvaldemar/essential-devops-books)\ 117 | 🖥️ Check out my [studio streaming and recording kit](https://kit.co/heyvaldemar/my-studio-streaming-and-recording-kit)\ 118 | 📡 Check out my [streaming starter kit](https://kit.co/heyvaldemar/streaming-starter-kit) 119 | 120 | ## Follow Me 121 | 122 | 🎬 [YouTube](https://www.youtube.com/channel/UCf85kQ0u1sYTTTyKVpxrlyQ?sub_confirmation=1)\ 123 | 🐦 [X / Twitter](https://twitter.com/heyvaldemar)\ 124 | 🎨 [Instagram](https://www.instagram.com/heyvaldemar/)\ 125 | 🐘 [Mastodon](https://mastodon.social/@heyvaldemar)\ 126 | 🧵 [Threads](https://www.threads.net/@heyvaldemar)\ 127 | 🎸 [Facebook](https://www.facebook.com/heyvaldemarFB/)\ 128 | 🧊 [Bluesky](https://bsky.app/profile/heyvaldemar.bsky.social)\ 129 | 🎥 [TikTok](https://www.tiktok.com/@heyvaldemar)\ 130 | 💻 [LinkedIn](https://www.linkedin.com/in/heyvaldemar/)\ 131 | 📣 [daily.dev Squad](https://app.daily.dev/squads/devopscompass)\ 132 | 🧩 [LeetCode](https://leetcode.com/u/heyvaldemar/)\ 133 | 🐈 [GitHub](https://github.com/heyvaldemar) 134 | 135 | ## Community of IT Experts 136 | 137 | 👾 [Discord](https://discord.gg/AJQGCCBcqf) 138 | 139 | ## Refill My Coffee Supplies 140 | 141 | 💖 [PayPal](https://www.paypal.com/paypalme/heyvaldemarCOM)\ 142 | 🏆 [Patreon](https://www.patreon.com/heyvaldemar)\ 143 | 💎 [GitHub](https://github.com/sponsors/heyvaldemar)\ 144 | 🥤 [BuyMeaCoffee](https://www.buymeacoffee.com/heyvaldemar)\ 145 | 🍪 [Ko-fi](https://ko-fi.com/heyvaldemar) 146 | 147 | 🌟 **Bitcoin (BTC):** bc1q2fq0k2lvdythdrj4ep20metjwnjuf7wccpckxc\ 148 | 🔹 **Ethereum (ETH):** 0x76C936F9366Fad39769CA5285b0Af1d975adacB8\ 149 | 🪙 **Binance Coin (BNB):** bnb1xnn6gg63lr2dgufngfr0lkq39kz8qltjt2v2g6\ 150 | 💠 **Litecoin (LTC):** LMGrhx8Jsx73h1pWY9FE8GB46nBytjvz8g 151 | 152 |
153 | 154 | ### Show some 💜 by starring some of the [repositories](https://github.com/heyValdemar?tab=repositories)! 155 | 156 | ![octocat](https://user-images.githubusercontent.com/10498744/210113490-e2fad07f-4488-4da8-a656-b9abbdd8cb26.gif) 157 | 158 |
159 | 160 | ![footer](https://user-images.githubusercontent.com/10498744/210157572-1fca0242-8af2-46a6-bfa3-666ffd40ebde.svg) 161 | -------------------------------------------------------------------------------- /wordpress-traefik-letsencrypt-docker-compose.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | wordpress-network: 3 | external: true 4 | traefik-network: 5 | external: true 6 | 7 | volumes: 8 | mariadb-data: 9 | wordpress-data: 10 | wordpress-mariadb-backup: 11 | wordpress-data-backups: 12 | wordpress-database-backups: 13 | traefik-certificates: 14 | 15 | services: 16 | mariadb: 17 | image: ${WORDPRESS_MARIADB_IMAGE_TAG} 18 | volumes: 19 | - mariadb-data:/var/lib/mysql 20 | environment: 21 | MARIADB_DATABASE: ${WORDPRESS_DB_NAME} 22 | MARIADB_USER: ${WORDPRESS_DB_USER} 23 | MARIADB_PASSWORD: ${WORDPRESS_DB_PASSWORD} 24 | MARIADB_ROOT_PASSWORD: ${WORDPRESS_DB_ADMIN_PASSWORD} 25 | networks: 26 | - wordpress-network 27 | healthcheck: 28 | test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] 29 | interval: 10s 30 | timeout: 5s 31 | retries: 3 32 | start_period: 60s 33 | restart: unless-stopped 34 | 35 | wordpress: 36 | image: ${WORDPRESS_IMAGE_TAG} 37 | volumes: 38 | - wordpress-data:${DATA_PATH} 39 | environment: 40 | WORDPRESS_DATABASE_HOST: mariadb 41 | WORDPRESS_DATABASE_PORT_NUMBER: 3306 42 | WORDPRESS_DATABASE_NAME: ${WORDPRESS_DB_NAME} 43 | WORDPRESS_DATABASE_USER: ${WORDPRESS_DB_USER} 44 | WORDPRESS_DATABASE_PASSWORD: ${WORDPRESS_DB_PASSWORD} 45 | WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX} 46 | WORDPRESS_BLOG_NAME: ${WORDPRESS_BLOG_NAME} 47 | WORDPRESS_FIRST_NAME: ${WORDPRESS_ADMIN_NAME} 48 | WORDPRESS_LAST_NAME: ${WORDPRESS_ADMIN_LASTNAME} 49 | WORDPRESS_USERNAME: ${WORDPRESS_ADMIN_USERNAME} 50 | WORDPRESS_PASSWORD: ${WORDPRESS_ADMIN_PASSWORD} 51 | WORDPRESS_EMAIL: ${WORDPRESS_ADMIN_EMAIL} 52 | WORDPRESS_SMTP_HOST: ${WORDPRESS_SMTP_ADDRESS} 53 | WORDPRESS_SMTP_PORT: ${WORDPRESS_SMTP_PORT} 54 | WORDPRESS_SMTP_USER: ${WORDPRESS_SMTP_USER_NAME} 55 | WORDPRESS_SMTP_PASSWORD: ${WORDPRESS_SMTP_PASSWORD} 56 | networks: 57 | - wordpress-network 58 | - traefik-network 59 | healthcheck: 60 | test: timeout 10s bash -c ':> /dev/tcp/127.0.0.1/8080' || exit 1 61 | interval: 10s 62 | timeout: 5s 63 | retries: 3 64 | start_period: 90s 65 | labels: 66 | # Enable Traefik for this container 67 | - "traefik.enable=true" 68 | # Match incoming requests on a specific hostname 69 | - "traefik.http.routers.wordpress.rule=Host(`${WORDPRESS_HOSTNAME}`)" 70 | # Assign the router to a named Traefik service 71 | - "traefik.http.routers.wordpress.service=wordpress" 72 | # Use the 'websecure' (HTTPS) entry point 73 | - "traefik.http.routers.wordpress.entrypoints=websecure" 74 | # Define the internal container port for routing 75 | - "traefik.http.services.wordpress.loadbalancer.server.port=8080" 76 | # Enable TLS on this router 77 | - "traefik.http.routers.wordpress.tls=true" 78 | # Use Let's Encrypt for certificate management 79 | - "traefik.http.routers.wordpress.tls.certresolver=letsencrypt" 80 | # Pass the original Host header to the container 81 | - "traefik.http.services.wordpress.loadbalancer.passhostheader=true" 82 | # Apply a compression middleware 83 | - "traefik.http.routers.wordpress.middlewares=compresstraefik" 84 | # Define settings for the compression middleware 85 | - "traefik.http.middlewares.compresstraefik.compress=true" 86 | # Specify which Docker network Traefik should use for routing 87 | - "traefik.docker.network=traefik-network" 88 | restart: unless-stopped 89 | depends_on: 90 | mariadb: 91 | condition: service_healthy 92 | traefik: 93 | condition: service_healthy 94 | 95 | traefik: 96 | image: ${TRAEFIK_IMAGE_TAG} 97 | command: 98 | # Set the log level (DEBUG, INFO, WARN, ERROR) 99 | - "--log.level=${TRAEFIK_LOG_LEVEL}" 100 | # Enable the built-in API and web-based dashboard 101 | - "--api.dashboard=true" 102 | # Enable the /ping endpoint so we can health-check Traefik 103 | - "--ping=true" 104 | # Assign the /ping endpoint to a dedicated entry point on port 8082 105 | - "--ping.entrypoint=ping" 106 | - "--entrypoints.ping.address=:8082" 107 | # Define the primary HTTP entry point on port 80 108 | - "--entrypoints.web.address=:80" 109 | # Define the secure (HTTPS) entry point on port 443 110 | - "--entrypoints.websecure.address=:443" 111 | # HTTP -> HTTPS redirect at entrypoint level (Traefik 3.2+) 112 | - "--entrypoints.web.http.redirections.entrypoint.to=websecure" 113 | - "--entrypoints.web.http.redirections.entrypoint.scheme=https" 114 | # Enable the Docker provider to detect containers and their labels 115 | - "--providers.docker=true" 116 | # Point Traefik to the Docker socket 117 | - "--providers.docker.endpoint=unix:///var/run/docker.sock" 118 | # Prevent automatic exposure of all containers; only expose containers 119 | # with "traefik.enable=true" 120 | - "--providers.docker.exposedbydefault=false" 121 | # Use ACME (Let's Encrypt) to generate/renew certificates via TLS challenge 122 | - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" 123 | # The email address used by Let's Encrypt for renewal notices 124 | - "--certificatesresolvers.letsencrypt.acme.email=${TRAEFIK_ACME_EMAIL}" 125 | # The file where ACME certificates are stored inside the container 126 | - "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json" 127 | # Enable Prometheus metrics 128 | - "--metrics.prometheus=true" 129 | # Configure Prometheus histogram buckets 130 | - "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0" 131 | # Check for newer Traefik versions and optionally log that info 132 | - "--global.checknewversion=true" 133 | # Disable sending anonymous usage data to the Traefik maintainers 134 | - "--global.sendanonymoususage=false" 135 | volumes: 136 | - /var/run/docker.sock:/var/run/docker.sock:ro 137 | - traefik-certificates:/etc/traefik/acme 138 | networks: 139 | - traefik-network 140 | ports: 141 | - "80:80" 142 | - "443:443" 143 | healthcheck: 144 | test: ["CMD", "wget", "http://localhost:8082/ping", "--spider"] 145 | interval: 10s 146 | timeout: 5s 147 | retries: 3 148 | start_period: 5s 149 | labels: 150 | # Enable Traefik for this container 151 | - "traefik.enable=true" 152 | # A router to expose the Traefik dashboard 153 | - "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_HOSTNAME}`)" 154 | - "traefik.http.routers.dashboard.entrypoints=websecure" 155 | - "traefik.http.routers.dashboard.tls=true" 156 | - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" 157 | - "traefik.http.routers.dashboard.service=api@internal" 158 | # Basic Authentication for the Traefik dashboard 159 | - "traefik.http.routers.dashboard.middlewares=authtraefik" 160 | - "traefik.http.middlewares.authtraefik.basicauth.users=${TRAEFIK_BASIC_AUTH}" 161 | # Specify the internal server port to the dashboard service 162 | - "traefik.http.services.dashboard.loadbalancer.server.port=8080" 163 | # Pass the original Host header to the backend 164 | - "traefik.http.services.dashboard.loadbalancer.passhostheader=true" 165 | # Redirect from non-www to www (permanent) 166 | - "traefik.http.routers.redirect-to-www.rule=Host(`${WORDPRESS_ROOT_DOMAIN}`)" 167 | - "traefik.http.routers.redirect-to-www.entrypoints=websecure" 168 | - "traefik.http.routers.redirect-to-www.tls=true" 169 | - "traefik.http.routers.redirect-to-www.tls.certresolver=letsencrypt" 170 | - "traefik.http.routers.redirect-to-www.middlewares=to-www" 171 | - "traefik.http.middlewares.to-www.redirectregex.regex=^https?://${WORDPRESS_ROOT_DOMAIN}(.*)" 172 | - "traefik.http.middlewares.to-www.redirectregex.replacement=https://www.${WORDPRESS_ROOT_DOMAIN}$$1" 173 | - "traefik.http.middlewares.to-www.redirectregex.permanent=true" 174 | restart: unless-stopped 175 | 176 | backups: 177 | image: ${WORDPRESS_MARIADB_IMAGE_TAG} 178 | command: >- 179 | sh -lc 'sleep $$BACKUP_INIT_SLEEP && 180 | while true; do 181 | mariadb-dump -h mariadb -u $$WORDPRESS_DB_USER -p"$$WORDPRESS_DB_PASSWORD" $$WORDPRESS_DB_NAME | 182 | gzip > "$$MARIADB_BACKUPS_PATH/$$MARIADB_BACKUP_NAME-$$(date +%Y-%m-%d_%H-%M).gz" && 183 | tar -zcpf "$$DATA_BACKUPS_PATH/$$DATA_BACKUP_NAME-$$(date +%Y-%m-%d_%H-%M).tar.gz" -C / "$${DATA_BACKUP_PATH#/}" && 184 | find $$MARIADB_BACKUPS_PATH -type f -mtime +$$MARIADB_BACKUP_PRUNE_DAYS -delete && 185 | find $$DATA_BACKUPS_PATH -type f -mtime +$$DATA_BACKUP_PRUNE_DAYS -delete; 186 | sleep $$BACKUP_INTERVAL; done' 187 | volumes: 188 | - wordpress-mariadb-backup:/var/lib/mysql 189 | - wordpress-data:${DATA_PATH} 190 | - wordpress-data-backups:${DATA_BACKUPS_PATH} 191 | - wordpress-database-backups:${MARIADB_BACKUPS_PATH} 192 | environment: 193 | WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME} 194 | WORDPRESS_DB_USER: ${WORDPRESS_DB_USER} 195 | WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD} 196 | BACKUP_INIT_SLEEP: ${BACKUP_INIT_SLEEP} 197 | BACKUP_INTERVAL: ${BACKUP_INTERVAL} 198 | MARIADB_BACKUP_PRUNE_DAYS: ${MARIADB_BACKUP_PRUNE_DAYS} 199 | DATA_BACKUP_PRUNE_DAYS: ${DATA_BACKUP_PRUNE_DAYS} 200 | MARIADB_BACKUPS_PATH: ${MARIADB_BACKUPS_PATH} 201 | DATA_BACKUPS_PATH: ${DATA_BACKUPS_PATH} 202 | DATA_PATH: ${DATA_PATH} 203 | MARIADB_BACKUP_NAME: ${MARIADB_BACKUP_NAME} 204 | DATA_BACKUP_NAME: ${DATA_BACKUP_NAME} 205 | networks: 206 | - wordpress-network 207 | restart: unless-stopped 208 | depends_on: 209 | mariadb: 210 | condition: service_healthy 211 | --------------------------------------------------------------------------------