Hardened version of official WordPress container, with special support for Kubernetes.
Features:
- Scheduled updates via wp-cli
- NGINX instead of Apache
- Supports NGINX-PROXY (VIRTUAL_HOST environment variable)
- Hardened settings for WordPress: limiting access to code execution from wp-content directory, basic auth on wp-login.php
- Basic Auth enabled by default to protect wp-login against bots (default user:
riotkit, password:riotkit), can be changed using environment variables - Non-root container
- Free from Supervisord, using lightweight multirun instead
- Runtime NGINX and PHP configuration to adjust things like
memory_limit,error_reportingorpost_max_size - Pre-configuration of admin account, website name and list of installed plugins
- Possible to upgrade WordPress together with docker container
- Built-in primitive rules to block common exploits targeting PHP
Kubernetes-only features:
- Helm installer
- Integration with Backup Repository (for Kubernetes-native backups)
- Integration with Volume Syncing Controller (for WordPress volume synchronization between Pod and cloud filesystem)
- Web Application Firewall and OWASP CRS support (experimental)
- Schedule SQL query execution periodically to perform tasks like deletion of old, unapproved comments
- Use GitHub Actions as CI
- Replace J2cli with P2cli
- Replace Supervisord with multirun
- Non-root container
- Helm Chart
- Plugins management - container installs selected plugins right after start or before starting
- Support for Network Policy templates
- Support for Backup Repository template
- Support WAF (Web Application Firewall) with OWASP CRS
- Real liveness and readiness checks
- PHP-FPM chroot (to verify first)
- Support for WP Super Cache plugin (https://www.nginx.com/blog/9-tips-for-improving-wordpress-performance-with-nginx/#wp-super-cache)
Disabling:
-e BASIC_AUTH_ENABLED=false
Changing password:
-e BASIC_AUTH_USER=some-user -e BASIC_AUTH_PASSWORD=some-password
https://github.com/riotkit-org/wordpress-hardened/packages
Example: ghcr.io/riotkit-org/wordpress-hardened:5.9.3-1
With docker command:
sudo docker run -v $(pwd)/your-www-files:/var/www/html -e WORDPRESS_DB_HOST=... -e WORDPRESS_DB_USER=... -e WORDPRESS_DB_PASSWORD=... -e WORDPRESS_DB_NAME=... -p 80:80 ghcr.io/riotkit-org/wordpress-hardened:5.9.3-1Or with docker-compose:
version: "2.3" services: app_your_app: image: ghcr.io/riotkit-org/wordpress-hardened:5.9.3-1 volumes: - ./your-www-files/:/var/www/html environment: WORDPRESS_DB_HOST: "db" WORDPRESS_DB_USER: "your_user" WORDPRESS_DB_PASSWORD: "${DB_PASSWORD_THERE}" WORDPRESS_DB_NAME: "your_app" WORDPRESS_TABLE_PREFIX: "wp_" AUTO_UPDATE_CRON: "0 5 * * SAT" XMLRPC_DISABLED: "true" DISABLE_DIRECT_CONTENT_PHP_EXECUTION: "false" ENABLED_PLUGINS: "amazon-s3-and-cloudfront" # basic auth on administrative endpoints BASIC_AUTH_ENABLED: "true" BASIC_AUTH_USER: john BASIC_AUTH_PASSWORD: secret # main page URL WP_PAGE_URL: "zsp.net.pl" # multiple domains can be pointing at this container VIRTUAL_HOST: "zsp.net.pl,www.zsp.net.pl,wroclaw.zsp.net.pl,wwww.wroclaw.zsp.net.pl"
You can skip installation wizard by installing WordPress on container startup.
This container uses wp-cli to install WordPress and plugins allowing you to prepare a fully automated website.
Example configuration:
WP_PREINSTALL: true WP_SITE_URL: example.org WP_SITE_ADMIN_LOGIN: admin WP_SITE_ADMIN_PASSWORD: riotkit WP_SITE_ADMIN_EMAIL: example@example.org # NOTICE: The plugins will be installed right after WordPress installation is finished, # this means that when `WP_PREINSTALL=false`, then the entrypoint will wait for user # to complete the installation wizard, then the plugins will be installed ENABLED_PLUGINS: "amazon-s3-and-cloudfront,classic-editor" WP_INSTALLATION_WAIT_INTERVAL: 20 # in seconds, how long to wait until the WordPress is installed to start installing plugins WP_PLUGINS_REINSTALL_RETRIES: 30 # 30 retries with 20s interval
Example log:
>> Checking if autoupdate should be scheduled... [scheduling at '0 5 * * TUE'] >> Writing to basic auth file - /opt/htpasswd Adding password for user riotkit >> Rendering configuration files... >> Installing Wordpress >> UID=65161, GID=65161 WordPress not found in /var/www/riotkit - copying now... sending incremental file list index.php liveness.php readiness.php ... wp-includes/widgets/class-wp-widget-text.php sent 58,545,704 bytes received 54,312 bytes 39,066,677.33 bytes/sec total size is 58,341,389 speedup is 1.00 Complete! WordPress has been successfully copied to /var/www/riotkit No 'wp-config.php' found in /var/www/riotkit, but 'WORDPRESS_...' variables supplied; copying 'wp-config-docker.php' (WORDPRESS_DB_HOST WORDPRESS_DB_NAME WORDPRESS_DB_PASSWORD WORDPRESS_DB_USER) Success: WordPress installed successfully. >> Installing plugin 'amazon-s3-and-cloudfront' Installing WP Offload Media Lite for Amazon S3, DigitalOcean Spaces, and Google Cloud Storage (2.6.2) Downloading installation package from https://downloads.wordpress.org/plugin/amazon-s3-and-cloudfront.2.6.2.zip... Unpacking the package... Installing the plugin... Plugin installed successfully. Success: Installed 1 of 1 plugins. >> Installing plugin 'classic-editor' Installing Classic Editor (1.6.2) Downloading installation package from https://downloads.wordpress.org/plugin/classic-editor.1.6.2.zip... Unpacking the package... Installing the plugin... Plugin installed successfully. Success: Installed 1 of 1 plugins.
- Plugins will be installed AFTER WordPress will be installed. Use
WP_PREINSTALL: trueto install WordPress immediately, else the plugins will be installed after user will finish installation process - There will be
WP_PLUGINS_REINSTALL_RETRIESretries of plugins installation - If at least one plugin installation will fail, then after exceeding maximum number of retries the container will exit
- Even if some plugins installation will fail, the rest will be installed (installation process does not exit immediately after first fail)
Point access and error logs to files, to stdout/stderr or disable logging by using environment variables.
ACCESS_LOG: /dev/stdout ERROR_LOG: /dev/stderr
ACCESS_LOG: /mnt/logs/access.log ERROR_LOG: /mnt/logs/error.log
ACCESS_LOG: off ERROR_LOG: off
In restricted environments you may want to deny all egress traffic from your WordPress instance, but let the WordPress update itself and install/upgrade plugins using a HTTP proxy, where you are in control of allowed internet destinations.
env: WP_PROXY_HOST: my-proxy.proxy.svc.cluster.local WP_PROXY_PORT: 8080 WP_PROXY_USERNAME: user WP_PROXY_PASSWORD: xxxxx WP_PROXY_BYPASS_HOSTS: localhost
wordpress-hardened provides both container image and Helm Chart.
Helm Chart can be installed from an OCI repository ghcr.io/riotkit-org/charts/wordpress-hardened - check all available versions there
# change version to non-latest :-)
helm pull oci://ghcr.io/riotkit-org/charts/wordpress-hardened --version 0.0-latest-master
helm install myrelease oci://ghcr.io/riotkit-org/charts/wordpress-hardened --version 0.0-latest-mastergit-clone-controller is a Kubernetes controller allowing to clone a GIT repository before a Pod is launched, can be used to automatically fetch your website theme within just few seconds before Pod starts.
Use and adjust following Helm values to clone your WordPress theme from a GIT repository before the application will start up.
values.yaml
# ---------------------------------------------------------- # theme # ---------------------------------------------------------- podLabels: riotkit.org/git-clone-controller: "true" podAnnotations: git-clone-controller/revision: main git-clone-controller/url: "https://git.example.org/my-example/my-theme.git" git-clone-controller/path: /var/www/riotkit/wp-content/themes/my-theme git-clone-controller/secretName: my-secret-name git-clone-controller/secretTokenKey: gitToken git-clone-controller/owner: "65161" git-clone-controller/group: "65161"
Automatically taking a snapshot of database + files using a CronJob can be configured within Helm Values. Requires to have a Backup Repository instance installed inside the cluster or outside the cluster.
values.yaml
backups: enabled: true schedule: "16 1 * * *" collectionId: "xxx"
⚠️ This is experimental and may not work yet.
Use following values to enable Web Application Firewall rules to protect your WordPress instance against various forms of attacks.
Learn more about Web Application Firewall setup - waf-proxy
values.yaml
waf: enabled: true env: ENABLE_RULE_WORDPRESS: true ENABLE_CRS: true ENABLE_RATE_LIMITER: true RATE_LIMIT_EVENTS: "30" RATE_LIMIT_WINDOW: "5s" ENABLE_CORAZA_WAF: true #DEBUG: true
i️ Works only if you are using ingress-nginx as Ingress Controller.
ingress-nginx has a built-in mod_security v3, that needs to be enabled on global configuration level using those helm values:
# Ingress NGINX config snippet controller: config: enable-modsecurity: "true" enable-owasp-modsecurity-crs: "true" modsecurity-snippet: | SecRuleEngine On
⚠️ This will enable mod_security and OWASP Core RuleSet on all ingress resources by default! According to documentation you need to set ingress annotationnginx.ingress.kubernetes.io/enable-modsecurity: "false"in every ingress, where you want the WAF to be disabled
WordPress requires additional tweaking, this can be done using Helm values of our Helm Chart as follows:
# WordPress Hardened config snippet ingresses: - name: wp-https className: nginx annotations: cert-manager.io/cluster-issuer: letsencrypt-staging # WAF provided by Ingress NGINX nginx.ingress.kubernetes.io/enable-modsecurity: "true" nginx.ingress.kubernetes.io/enable-owasp-core-rules: "true" nginx.ingress.kubernetes.io/modsecurity-transaction-id: "$request_id" nginx.ingress.kubernetes.io/modsecurity-snippet: | SecRuleEngine On SecAction "id:900130,phase:1,nolog,pass,t:none,setvar:tx.crs_exclusions_drupal=0,setvar:tx.crs_exclusions_wordpress=1,setvar:tx.crs_exclusions_nextcloud=0,setvar:tx.crs_exclusions_dokuwiki=0,setvar:tx.crs_exclusions_cpanel=0" hosts: - host: my-domain.org paths: - path: / pathType: ImplementationSpecific tls: - hosts: ["my-domain.org"] secretName: my-domain-tls
Every file placed in /mnt/extra-files will be copied during startup to /var/www/riotkit/, this mechanism ensures that
no any file will be created with root-permissions inside a /var/www/riotkit directory - mounting a volume directly could do so.
# create extra ConfigMaps extraConfigMaps: - name: my-configmap-name data: something.php: | <?php echo "Hello anarchism!"; # create extra mounts pv: # Pod-level volumes section extraVolumes: - name: my-config configMap: name: my-configmap-name # Container-level volumeMounts section extraVolumeMounts: - name: my-config mountPath: /mnt/extra-files/wp-content/some-file.php subPath: some-file.php
To clean up the database out of rubbish there is a possibility to define SQL commands that would be executed right after deployment, or periodically.
# (...) db: # (...) administrativeJobs: # This Job executes right after deployment - install/upgrade test-1: image: bitnami/mariadb:10.6 isHelmHook: true # language=mysql sql: | SHOW TABLES; # CronJob delete-old-unapproved-comments: image: bitnami/mariadb:10.6 isHelmHook: false schedule: "*/30 * * * *" # language=mysql sql: | DELETE FROM wp_comments WHERE comment_approved = 0 AND DATEDIFF(NOW(), comment_date) > 7;
Project was started as a part of RiotKit initiative, for the needs of grassroot organizations such as:
- Fighting for better working conditions syndicalist (International Workers Association for example)
- Tenants rights organizations
- Political prisoners supporting organizations
- Various grassroot organizations that are helping people to organize themselves without authority
We provide tools that organizations can host themselves with trust.