This project provides a template for modern WordPress application development using Docker for the local environment, BrowserSync for Live Reload, and GitHub Actions for Continuous Integration and Deployment (CI/CD) to a DigitalOcean Droplet (or any other Linux server with Docker).
- Overview
- Prerequisites
- Getting Started (Local Development)
- Project Structure
- Environment Variables (
.env) - Server Setup & Deployment (CI/CD)
- Developing Custom Themes and Plugins
- Troubleshooting
- Further Customization / Advanced Topics
- Security Reminders
This template provides:
- An isolated local development environment powered by Docker (WordPress, MySQL, phpMyAdmin).
- A custom WordPress Docker image with Composer pre-installed.
- Live Reload and CSS hot-swapping using BrowserSync.
- Integration with PHP_CodeSniffer for WordPress Coding Standards.
- A ready-to-use CI/CD pipeline with GitHub Actions for automatic deployment to a server upon push to the
mainbranch. - PHP dependency management via Composer (scoped to
wp-content). - Example configuration for increasing PHP upload limits for the production environment.
Before you begin, ensure you have the following tools installed on your host machine:
- Docker Desktop (for macOS or Windows) or Docker Engine + Docker Compose (for Linux). Docker Official Website
- Node.js and npm: Required for installing and running BrowserSync. Node.js Official Website
- Git: For cloning the repository and version control. Git Official Website
- An IDE or Text Editor: PhpStorm is recommended for its PHP integration and tools, but any editor will work.
Clone this repository to your local machine:
git clone <YOUR_REPOSITORY_URL>
cd <repository-folder-name>In the project root, you'll find an .env.example file. Copy it to a new file named .env:
cp .env.example .envOpen the .env file and customize the variable values (passwords, ports, etc.) to suit your local setup. See the "Environment Variables" section below for details on each variable.
This command will build the custom WordPress Docker image (defined in Dockerfile) if it doesn't exist or if the Dockerfile has changed, and then it will start all services (WordPress, MySQL, phpMyAdmin) in detached mode:
docker compose up -d --buildFor subsequent starts, if the Dockerfile or docker-compose.yml configuration hasn't changed, you can typically just use docker compose up -d to start the containers.
- WordPress Site:
http://localhost:${WORDPRESS_HOST_PORT}(the default port is8000ifWORDPRESS_HOST_PORTis not specified in your local.env). On the first run, you'll need to go through the standard WordPress installation process. - phpMyAdmin:
http://localhost:${PHPMYADMIN_HOST_PORT}(the default port is8081). Use the MySQL credentials (MYSQL_USER,MYSQL_PASSWORDorMYSQL_ROOT_PASSWORD) from your local.envfile to log in. - MySQL Database: The database is accessible to the WordPress container at host
dbon port3306. If you've exposed the MySQL port on your host machine (viaMYSQL_HOST_PORTin.env), you can connect to it from your host using an SQL client.
Composer is installed globally within the wordpress Docker image. All Composer commands related to wp-content dependencies (e.g., WordPress plugins, themes managed via Composer) should be run inside the wordpress container, within the /var/www/html/wp-content/ directory.
- Exec into the
wordpresscontainer:docker compose exec wordpress bash - Navigate to the
wp-contentdirectory:# Inside the container cd /var/www/html/wp-content
- Run Composer commands as needed:
# Example: Install dependencies defined in wp-content/composer.json composer install --no-progress --no-suggest # Example: Require a new WordPress plugin from WPackagist composer require wpackagist-plugin/plugin-slug --no-progress --no-suggest # Example: Update a specific package composer update wpackagist-plugin/plugin-slug --with-all-dependencies
This project is configured with tools to help maintain code quality and standards:
PHP_CodeSniffer, along with WordPress Coding Standards (WPCS), is set up.
- Configuration:
wp-content/phpcs.xml.dist - Composer Script:
lint:cs(defined inwp-content/composer.json)
To run the code standards check:
- Exec into the
wordpresscontainer and navigate to/var/www/html/wp-content(if not already there). - Run the script:
# Inside the container, in /var/www/html/wp-content composer lint:cs
To automatically fix some of the reported coding standard issues:
# Inside the container, in /var/www/html/wp-content
composer fix:csPHPStan is set up using the lipemat/phpstan-wordpress extension to help find potential bugs in your code.
- Configuration:
wp-content/phpstan.neon.dist - Composer Script:
lint:stan(defined inwp-content/composer.json)
To run static analysis:
- Ensure
lipemat/phpstan-wordpress(and its dependencies likephpstan/phpstan) are installed viacomposer updateorcomposer requireas previously discussed. - Make sure you have a
wp-content/phpstan.neon.distconfiguration file. A basic example was provided earlier, but you should tailor it using thelipemat/phpstan-wordpressdocumentation (often involving anincludes:line pointing tovendor/lipemat/phpstan-wordpress/extension.neon). - Exec into the
wordpresscontainer and navigate to/var/www/html/wp-content(if not already there). - Run the script:
# Inside the container, in /var/www/html/wp-content composer lint:stan
IDE Integration: You can also integrate both PHP_CodeSniffer and PHPStan with your IDE (e.g., PhpStorm) for real-time linting and analysis by configuring them to use the PHP interpreter and tools from the Docker container.
BrowserSync is used for automatic browser reloading and CSS injection during local development.
- Ensure Node.js and npm are installed on your host machine.
- Install the project's Node.js dependencies (which include
browser-syncanddotenv):# In the project root on your host machine npm install - Ensure your WordPress Docker containers are running (
docker compose up -d). - Start BrowserSync:
# In the project root on your host machine npm run watch - Open the URL provided by BrowserSync in your browser (usually
http://localhost:3000). Use this BrowserSync URL for development to see live updates. BrowserSync monitors files within thewp-content/directory (as configured inbs-config.js) and will automatically:- Inject CSS changes "on the fly" without a full page reload.
- Reload the page on PHP or JS file changes.
The WordPress site URL that BrowserSync proxies is taken from the
WORDPRESS_HOST_PORTin your local.envfile.
A brief overview of key files and directories in this template:
.github/: Contains GitHub Actions workflow configurations.workflows/deploy.yml: Defines the CI/CD pipeline for deploying to the server.
config/: Contains configuration files for services.php/uploads.ini: Custom PHP settings (e.g., for upload limits), used by the production WordPress container.
scripts/: Contains utility scripts.server_composer_commands.sh: Script executed on the server by the CI/CD pipeline to run Composer install commands inside the WordPress container forwp-contentand any plugins/themes that have their owncomposer.json.
wp-content/: The standard WordPress content directory. This is where you'll do most of your WordPress-specific development.themes/: Place your custom themes here (e.g.,my-custom-theme).plugins/: Place your custom plugins here, or plugins managed by Composer will be installed here.mu-plugins/: For Must-Use plugins.composer.json/composer.lock: Manages PHP dependencies forwp-content(e.g., linters, WordPress plugins/themes from WPackagist).phpcs.xml.dist: Configuration file for PHP_CodeSniffer (WordPress Coding Standards).phpstan.neon.dist: Configuration file for PHPStan static analysis.
.env: Local environment variables (created from.env.example, ignored by Git)..env.example: Template for local environment variables.docker-compose.yml: Docker Compose configuration for the local development environment.docker-compose.prod.yml: Docker Compose configuration for the production/staging server (this file is deployed by the CI/CD workflow).Dockerfile: Defines the custom WordPress Docker image used locally and on the server, which includes Composer.bs-config.js: Configuration file for BrowserSync (for Live Reload).package.json/package-lock.json: Manages Node.js dependencies for the project (e.g., BrowserSync, dotenv)..gitignore: Specifies intentionally untracked files that Git should ignore.README.md: This file – providing guidance on using the template.LICENSE: Contains the MIT License text for the project.
This project uses .env files to manage environment-specific configurations. An .env.example file is provided as a template. You should copy it to .env for your local setup and fill in the appropriate values. The .env file itself should be listed in your .gitignore file and never committed to version control, especially if it contains sensitive credentials.
This file (created by copying .env.example in the project root) configures your local development environment.
-
PROJECT_NAME:- Purpose: A prefix for your local Docker container names (e.g.,
my_wp_project). This helps in identifying containers if you have multiple Docker projects. - Example:
my_local_wordpress
- Purpose: A prefix for your local Docker container names (e.g.,
-
MYSQL_ROOT_PASSWORD:- Purpose: The root password for your local MySQL database service.
- Example:
local_strong_mysql_root_password(use a strong password even locally)
-
MYSQL_DATABASE:- Purpose: The name of the database that will be created for your local WordPress installation.
- Example:
wordpress_local_db
-
MYSQL_USER:- Purpose: The username that WordPress will use to connect to the local MySQL database.
- Example:
wp_local_user
-
MYSQL_PASSWORD:- Purpose: The password for the
MYSQL_USER. - Example:
local_strong_wp_user_password
- Purpose: The password for the
-
MYSQL_HOST_PORT:- Purpose: (Optional) If you want to access the MySQL database directly from your host machine (e.g., with an SQL client), this variable maps a port on your host to the MySQL container's port 3306.
- Example:
33060(then connect tolocalhost:33060from host) - Default if unset/empty:
3306(indocker-compose.yml)
-
WORDPRESS_HOST_PORT:- Purpose: The port on your host machine through which you will access your local WordPress site in the browser. This is also the port that BrowserSync will proxy.
- Example:
8000(site accessible athttp://localhost:8000) - Default if unset/empty:
8000(indocker-compose.yml)
-
WORDPRESS_TABLE_PREFIX:- Purpose: The table prefix for your WordPress database tables.
- Example:
wp_local_ - Default if unset/empty:
wp_(indocker-compose.yml)
-
WORDPRESS_DEBUG:- Purpose: Set to
1to enable WordPress debug mode locally, or0to disable. Helpful for development. - Example:
1 - Default if unset/empty:
1(indocker-compose.yml)
- Purpose: Set to
-
PHPMYADMIN_HOST_PORT:- Purpose: The port on your host machine to access phpMyAdmin for managing your local database.
- Example:
8081(phpMyAdmin accessible athttp://localhost:8081) - Default if unset/empty:
8081(indocker-compose.yml)
This file is generated on the server (e.g., in /home/wordpress-template/) by the CI/CD workflow (.github/workflows/deploy.yml) using GitHub Secrets. You must configure these secrets in your GitHub repository settings. This server .env file should never be manually created on the server or committed to the repository.
-
GitHub Secret
PROD_PROJECT_NAME(writesPROD_PROJECT_NAMEto server.env):- Purpose: Project name prefix for server Docker containers.
- Example Value in GitHub Secret:
my_live_site
-
GitHub Secret
PROD_MYSQL_ROOT_PASSWORD(writesPROD_MYSQL_ROOT_PASSWORDto server.env):- Purpose: Strong, unique root password for the MySQL database on the server.
-
GitHub Secret
PROD_MYSQL_DATABASE(writesPROD_MYSQL_DATABASEto server.env):- Purpose: Name of the WordPress database on the server.
-
GitHub Secret
PROD_MYSQL_USER(writesPROD_MYSQL_USERto server.env):- Purpose: Username for WordPress to access the database on the server.
-
GitHub Secret
PROD_MYSQL_PASSWORD(writesPROD_MYSQL_PASSWORDto server.env):- Purpose: Strong, unique password for the WordPress database user on the server.
-
GitHub Secret
PROD_WORDPRESS_HOST_PORT(writesPROD_WORDPRESS_HOST_PORTto server.env):- Purpose: The port the WordPress container will expose on the server.
- Example Value in GitHub Secret:
80or8001.
-
GitHub Secret
PROD_WORDPRESS_TABLE_PREFIX(writesPROD_WORDPRESS_TABLE_PREFIXto server.env):- Purpose: WordPress table prefix on the server.
- Example Value in GitHub Secret:
wp_(Defaults towp_indocker-compose.prod.ymlif this variable is empty or not provided in the server.env).
-
GitHub Secret
PROD_WORDPRESS_DEBUG(writesPROD_WORDPRESS_DEBUGto server.env):- Purpose: WordPress debug mode on the server. Must be
0for production. - Example Value in GitHub Secret:
0.
- Purpose: WordPress debug mode on the server. Must be
-
GitHub Secret
PROD_WP_ENV(writesPROD_WP_ENVto server.env):- Purpose: Sets the WordPress environment type.
- Example Value in GitHub Secret:
productionorstaging.
-
GitHub Secret
PROD_WORDPRESS_CONFIG_EXTRA(writesPROD_WORDPRESS_CONFIG_EXTRAto server.env):- Purpose: Sets the WordPress extra configs.
- Example Value in GitHub Secret:
define('FS_METHOD', 'direct');.
-
GitHub Secrets for WordPress Salts/Keys (e.g.,
AUTH_KEY,SECURE_AUTH_KEY, etc.):- Purpose: Unique WordPress security keys and salts. Critical for security.
- GitHub Secret Names:
AUTH_KEY,SECURE_AUTH_KEY,LOGGED_IN_KEY,NONCE_KEY,AUTH_SALT,SECURE_AUTH_SALT,LOGGED_IN_SALT,NONCE_SALT(these are the names you create in GitHub Secrets UI, without aPROD_prefix). - Action: Generate these from https://api.wordpress.org/secret-key/1.1/salt/ and store each one as a separate GitHub Secret under its respective name (e.g., the value for
define('AUTH_KEY', 'value');goes into the GitHub Secret namedAUTH_KEY). Copy only the value part (what's between the single quotes in thedefine()statement). - How they are used: The GitHub Actions workflow (
deploy.yml) is configured to read these secrets (e.g.,${{ secrets.AUTH_KEY }}) and then write them into the server's.envfile with aPROD_prefix (e.g., asPROD_AUTH_KEY='value_from_AUTH_KEY_secret'). This ensures consistency in the server.envfile while allowing you to name the GitHub Secrets for salts more simply. Yourdocker-compose.prod.ymlthen expects variables like${PROD_AUTH_KEY}.
-
GitHub Secret
PROD_PHPMYADMIN_UPLOAD_LIMIT(writesPROD_PHPMYADMIN_UPLOAD_LIMITto server.env):- Purpose: Sets the
UPLOAD_LIMITfor phpMyAdmin on the server. - Example Value in GitHub Secret:
1G,256M. - Default if unset/empty in
.env:1G(indocker-compose.prod.yml)
- Purpose: Sets the
-
GitHub Secret
PROD_DOMAIN_NAME(writesPROD_DOMAIN_NAMEto server.env):- Purpose: Sets the domain for your server.
- Example Value in GitHub Secret:
yourdomain.com.
-
GitHub Secret
PROD_LETSENCRYPT_EMAIL(writesPROD_LETSENCRYPT_EMAILto server.env):- Purpose: Sets the email for your server LETSENCRYPT.
- Example Value in GitHub Secret:
[email protected].
-
GitHub Secret
PROD_PHPMYADMIN_AUTH_USER(writesPROD_PHPMYADMIN_AUTH_USERto server.env):- Purpose: Sets the login for proxied phpMyAdmin.
- Example Value in GitHub Secret:
admin.
-
GitHub Secret
PROD_PHPMYADMIN_AUTH_PASS(writesPROD_PHPMYADMIN_AUTH_PASSto server.env):- Purpose: Sets the hashed password for proxied phpMyAdmin. (docker run --rm httpd:alpine htpasswd -nb admin 'your password')
- Example Value in GitHub Secret:
hash of password.
This template includes a GitHub Actions workflow to automatically deploy your WordPress application to a production/staging server when you push changes to your main repository branch.
To use the CI/CD workflow for deployment, your server (e.g., a DigitalOcean Droplet) must meet the following basic requirements:
- Linux Operating System: Ubuntu LTS (e.g., 20.04, 22.04) is recommended and assumed for these instructions.
- Docker Engine: Must be installed on the server.
- Docker Compose (plugin version): The
docker composecommand (V2 syntax, without a hyphen) must be available. - SSH Access: GitHub Actions will connect to your server via SSH. Key-based authentication is required for the deployment user.
- Firewall (e.g., UFW): Configured to allow incoming traffic on necessary ports:
- SSH (typically port 22).
- HTTP (port 80).
- HTTPS (port 443).
- Project Directory: A directory on the server where the application files will be deployed (e.g.,
/home/wordpress-template/as configured inSERVER_PROJECT_PATHsecret). The SSH user used by GitHub Actions must have write permissions to this directory.
(Refer to earlier parts of this conversation or standard server setup guides for detailed instructions on installing Docker, Docker Compose, and configuring UFW if you are setting up a new server from scratch.)
Navigate to your GitHub repository settings: Settings -> Secrets and variables -> Actions -> New repository secret. Create the following secrets. These are essential for the CI/CD workflow to connect to your server and configure the application.
-
SSH_HOST:- Description: The IP address or hostname of your deployment server.
-
SSH_USER:- Description: The username for SSH login on your server (e.g.,
root, or a dedicated non-root deploy user with appropriate permissions).
- Description: The username for SSH login on your server (e.g.,
-
SSH_PRIVATE_KEY:- Description: The private SSH key (generated specifically for CI/CD) that has authorized access to your server for the
SSH_USER. The corresponding public key must be in the user's~/.ssh/authorized_keysfile on the server.
- Description: The private SSH key (generated specifically for CI/CD) that has authorized access to your server for the
-
SERVER_PROJECT_PATH:- Description: The absolute path on your server where the project files will be deployed.
- Example Value:
/home/wordpress-template/
-
Project & Database Configuration Secrets (these will be written to the server's
.envfile, prefixed withPROD_):PROD_PROJECT_NAME(e.g.,my_live_site)PROD_MYSQL_ROOT_PASSWORD(use a strong, unique password)PROD_MYSQL_DATABASEPROD_MYSQL_USERPROD_MYSQL_PASSWORD(use a strong, unique password for this user)PROD_WORDPRESS_HOST_PORT(e.g.,80or an internal port like8001if behind a reverse proxy)PROD_WORDPRESS_TABLE_PREFIX(e.g.,wp_; optional if defaultwp_is fine)PROD_WORDPRESS_DEBUG(should be0for production)PROD_WP_ENV(e.g.,productionorstaging)WORDPRESS_CONFIG_EXTRA(e.g.,define('FS_METHOD', 'direct'); if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { $_SERVER['HTTPS'] = 'on'; } define('WP_HOME', 'your domain'); define('WP_SITEURL', 'your domain'); if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST']; })
-
WordPress Salts/Keys Secrets (these GitHub Secrets are named without a
PROD_prefix):- Description: Unique WordPress security keys and salts. Generate these from https://api.wordpress.org/secret-key/1.1/salt/. Copy only the value part (what's between the single quotes in the
define()statement from the generator) into each corresponding GitHub Secret. - GitHub Secret Names:
AUTH_KEYSECURE_AUTH_KEYLOGGED_IN_KEYNONCE_KEYAUTH_SALTSECURE_AUTH_SALTLOGGED_IN_SALTNONCE_SALT
- Usage: The CI/CD workflow reads these secrets and writes them to the server's
.envfile with aPROD_prefix (e.g., the GitHub SecretAUTH_KEYbecomesPROD_AUTH_KEY='value'in the server.envfile).
- Description: Unique WordPress security keys and salts. Generate these from https://api.wordpress.org/secret-key/1.1/salt/. Copy only the value part (what's between the single quotes in the
-
phpMyAdmin Configuration Secrets (optional, if deploying phpMyAdmin; these will be written to the server's
.envfile prefixed withPROD_):PROD_PHPMYADMIN_UPLOAD_LIMIT(e.g.,1Gor256M)
-
LETSENCRYPT Configuration secrets:
PROD_DOMAIN_NAME(e.g.,yourdomain.com)PROD_LETSENCRYPT_EMAIL(e.g.,[email protected])PROD_PHPMYADMIN_AUTH_USER(e.g.,phpMyAdmin login)PROD_PHPMYADMIN_AUTH_PASS(e.g.,phpMyAdmin hashed password)
The CI/CD workflow is defined in the file .github/workflows/deploy.yml in your repository.
- Trigger: The workflow is typically configured to run automatically on a
pushto a specific branch (e.g.,mainormaster). You can customize this in thedeploy.ymlfile. - Key Workflow Steps:
- Checkout Code: The latest version of your code is checked out from the repository branch that triggered the workflow.
- Create Server
.envFile: A.envfile is dynamically generated by the workflow using the GitHub Secrets you configured. This file contains all the production-specific environment variables for your WordPress application and Docker services. - Deploy Files to Server: The workflow uses
rsync(via a secure SSH connection) to synchronize your project files (includingwp-content/, the server-specificdocker-compose.prod.yml, the generated.envfile, and theconfig/php/uploads.inifile) to theSERVER_PROJECT_PATHon your server. The--deleteoption inrsyncensures that files removed from the repository are also removed from the server in the target directory (excluding.gitfiles). - Execute Remote Commands on Server: After files are deployed, the workflow connects to your server again via SSH and executes the following commands in your
SERVER_PROJECT_PATH:docker compose -f docker-compose.prod.yml pull: Pulls the latest versions of any base Docker images specified in your server's Docker Compose file (e.g.,wordpress:phpX.X-apache,mysql:X.X).docker compose -f docker-compose.prod.yml up -d --force-recreate --remove-orphans: Stops, removes, and recreates your Docker containers based on thedocker-compose.prod.ymland the newly deployed files.--force-recreateensures containers are rebuilt even if only configuration or mounted files changed.--remove-orphanscleans up containers for services that are no longer defined.docker compose -f docker-compose.prod.yml exec -T wordpress_prod composer install --no-interaction --no-progress --no-dev --optimize-autoloader --working-dir=/var/www/html/wp-content: Runscomposer installinside the runningwordpress_prodcontainer to install/update PHP dependencies (defined inwp-content/composer.json) for the production environment (excluding dev dependencies).
- Monitoring: You can monitor the progress and logs of each deployment in the "Actions" tab of your GitHub repository.
This template is designed to streamline the development of custom WordPress themes and plugins.
-
Themes:
- Create your custom theme directories within the
wp-content/themes/folder in your local project. - For example:
wp-content/themes/my-custom-theme/. - Standard WordPress theme development practices apply. Ensure your theme has at least an
index.phpandstyle.csswith the correct theme headers. - If your theme uses Composer for PHP dependencies, manage them with a
composer.jsonfile inside your theme's directory, or use the mainwp-content/composer.json. - If your theme requires a frontend build process (SCSS, modern JS), set up your preferred build tools (like Vite or Webpack, as discussed) inside your theme's directory. This typically involves a
package.jsonand the build tool's configuration file (e.g.,vite.config.jsorwebpack.config.js) within your theme folder.
- Create your custom theme directories within the
-
Plugins:
- Create your custom plugin directories within the
wp-content/plugins/folder in your local project. - For example:
wp-content/plugins/my-custom-plugin/. - Ensure your main plugin PHP file has a valid WordPress plugin header comment, including at least
Plugin Name:. - If your plugin uses Composer for PHP dependencies, manage them with a
composer.jsonfile inside your plugin's directory, or use the mainwp-content/composer.json. - For frontend assets requiring a build process, set up build tools (Vite, Webpack, etc.) inside your plugin's directory, similar to themes.
- Create your custom plugin directories within the
-
Composer Dependencies for
wp-content:- You can manage shared PHP libraries or WordPress plugins/themes (from WPackagist or other repositories) using the
composer.jsonfile located in thewp-content/directory. - Run Composer commands (
install,require,update) inside thewordpressDocker container within the/var/www/html/wp-content/directory, as described in the "Getting Started" section. - Remember to commit your
wp-content/composer.jsonandwp-content/composer.lockfiles. Thewp-content/vendor/directory and any plugins/themes installed by this Composer instance should typically be added to your main.gitignoreif they are re-installed by the CI/CD pipeline on the server.
- You can manage shared PHP libraries or WordPress plugins/themes (from WPackagist or other repositories) using the
-
Frontend Assets Build Process (if using Vite/Webpack per theme/plugin):
- Navigate to your specific theme or plugin directory in your terminal on the host machine (e.g.,
cd wp-content/themes/my-custom-theme). - Run the development script (e.g.,
npm run devornpm run start) to start the Vite/Webpack development server for that specific theme/plugin. This server will handle HMR for its assets. - Ensure your theme/plugin's PHP code correctly enqueues assets from the Vite/Webpack dev server URL during development, and from a
dist/folder (using a manifest file) in production builds. - BrowserSync (run via
npm run watchfrom the project root) can still be used alongside for general PHP file watching and full-page reloads, complementing the HMR from Vite/Webpack for specific assets.
- Navigate to your specific theme or plugin directory in your terminal on the host machine (e.g.,
-
Linting:
- Use
composer lint:cs(for PHP_CodeSniffer) andcomposer lint:stan(if PHPStan is configured) inside thewordpresscontainer (in/var/www/html/wp-content/) to check your code. - Configure your IDE (e.g., PhpStorm) to use the Dockerized PHP interpreter and linters for real-time feedback.
- Use
-
Version Control (Git):
- Commit all your custom code for themes and plugins.
- Commit configuration files (
docker-compose.yml,docker-compose.prod.yml,Dockerfile,bs-config.js, projectpackage.json,wp-content/composer.json,wp-content/composer.lock,wp-content/phpcs.xml.dist, etc.). - Ensure your main
.gitignorefile correctly ignores:- Local
.envfile. node_modules/directory in the project root.wp-content/vendor/directory (if PHP dependencies are installed by CI/CD on the server).- Any plugins/themes in
wp-content/plugins/orwp-content/themes/that are installed bywp-content/composer.jsonon the server (to avoid conflicts withrsync --delete). However, make sure your custom-developed themes and plugins are NOT ignored.
- Local
- Example relevant lines for your root
.gitignore:# Environment files .env *.env.local # Node dependencies /node_modules/ # WordPress content specific vendor directory /wp-content/vendor/ # Example: Ignore specific Composer-installed plugins/themes if they are not part of your direct development # but are pulled in by wp-content/composer.json on the server. # /wp-content/plugins/akismet/ # /wp-content/plugins/contact-form-7/ # Make sure to NOT ignore your custom themes/plugins: # !/wp-content/plugins/my-custom-plugin/ # !/wp-content/themes/my-custom-theme/
The strategy for gitignoring Composer-managed plugins/themes in
wp-content/pluginsandwp-content/themesdepends on whether you commit them or let CI/CD install them. If CI/CD installs them, they should be gitignored.
This section lists common issues you might encounter and how to resolve them.
-
"Error establishing a database connection" on the server:
- Check Docker container logs: On your server, navigate to your project directory (e.g.,
/home/wordpress-template/) and rundocker compose -f docker-compose.prod.yml logs db_prodanddocker compose -f docker-compose.prod.yml logs wordpress_prod. Look for errors. - MySQL (
db_prod) not running: The most common cause for this on a new or small server is insufficient RAM. MySQL 8+ typically needs at least 1GB RAM to start and run reliably with WordPress. If logs show "Killed" messages or MySQL failing to start, consider upgrading your Droplet/server RAM. - Verify server
.envfile: Ensure the.envfile in your server's project directory (created by the CI/CD workflow from GitHub Secrets) contains the correct database credentials (PROD_MYSQL_DATABASE,PROD_MYSQL_USER,PROD_MYSQL_PASSWORD) and that these match what MySQL expects. - WordPress Salts/Keys: Ensure all
PROD_..._KEYandPROD_..._SALTvariables in the server.envfile are populated with unique, complex values from your GitHub Secrets. - Database/User Initialization: MySQL creates the database and user only on the first run with an empty data volume. If you updated credentials in GitHub Secrets after the database was already initialized, you might need to reset the database volume to force re-initialization with the new credentials.
- Warning: This deletes all database data! On the server:
cd /home/wordpress-template/ # Or your project path docker compose -f docker-compose.prod.yml down -v # Then re-run your GitHub Actions workflow to deploy and re-initialize.
- Warning: This deletes all database data! On the server:
- Check Docker container logs: On your server, navigate to your project directory (e.g.,
-
Local: File changes not appearing / new custom plugins not visible:
- Check
docker-compose.ymlvolume mount: Ensure your localdocker-compose.ymlcorrectly mounts./wp-content:/var/www/html/wp-content. - Restart Docker containers: This often resolves file sync issues, especially on macOS/Windows:
# In your local project root docker-compose down docker-compose up -d - Docker Desktop File Sharing Settings: (macOS/Windows) Verify that your project directory is included in Docker Desktop's "File Sharing" settings (Settings -> Resources -> File Sharing).
- Plugin Headers: Ensure your custom plugin's main PHP file has a correct WordPress plugin header comment, especially
Plugin Name:. - WordPress/Browser Cache: Hard refresh (Cmd+Shift+R or Ctrl+Shift+R) the "Plugins" page in your local WordPress admin.
- Correct Port: When developing locally:
- Access your site directly via the WordPress port (e.g.,
http://localhost:8000as perWORDPRESS_HOST_PORTin.env). - When
npm run watchis active for BrowserSync, access the site via the BrowserSync proxy port (e.g.,http://localhost:3000) to see live reloads and CSS injections.
- Access your site directly via the WordPress port (e.g.,
- Check
-
BrowserSync not injecting CSS "on the fly" (local):
- View through BrowserSync Port: Ensure you are viewing the site via the BrowserSync proxy URL (e.g.,
http://localhost:3000). - File Watching: Check that the specific CSS file you are editing is covered by the
filespatterns inbs-config.js(it currently watcheswp-content/**/*.css). - WordPress Enqueueing: Verify how your theme's
functions.phpis enqueuing the stylesheet (wp_enqueue_style). - Block Themes (
theme.json): If using a block theme, most styling is controlled bytheme.jsonand Global Styles. Changes to the theme's rootstyle.cssmight have limited visual effect or be overridden. Fortheme.jsonchanges to trigger a reload, add it to thefilesarray inbs-config.js. BrowserSync will then perform a full page reload.
- View through BrowserSync Port: Ensure you are viewing the site via the BrowserSync proxy URL (e.g.,
-
GitHub Actions workflow failures:
- Check Workflow Logs: Carefully review the logs for the failing step in the "Actions" tab of your GitHub repository.
- GitHub Secrets: Double-check that all required secrets (
SSH_HOST,SSH_USER,SSH_PRIVATE_KEY,SERVER_PROJECT_PATH, and allPROD_...variables) are correctly named and populated in your repository's Actions secrets settings. - SSH Connection Issues: Verify the SSH private key, host, and user. Ensure the server's firewall (UFW) allows SSH connections from GitHub Actions runners (usually, allowing from
Anywhereis necessary for SSH key-based auth for Actions, but ensure your key is strong). - Permissions on Server: Ensure the
SERVER_PROJECT_PATHdirectory on the server is writable by theSSH_USERthat GitHub Actions uses. - Server Disk Space: Ensure your server has enough free disk space.
- Errors in Remote Commands: Check the script block in the
Execute remote commandsstep for any syntax errors or commands failing on the server.
-
Composer issues (local or server):
- Run in correct directory: Ensure Composer commands are run from
wp-content/(or/var/www/html/wp-content/inside the container). - Memory limits: PHP might run out of memory for complex Composer operations. The WordPress Docker image usually has a reasonable memory limit for PHP CLI.
- Network issues: If Composer can't download packages, check network connectivity from the environment where Composer is running (e.g., from inside the Docker container for server-side
composer install). composer.lockissues: If you suspect a corrupted or out-of-synccomposer.lockfile, you can try deleting it (after backing it up) and runningcomposer installagain (which will regenerate it based oncomposer.json).
- Run in correct directory: Ensure Composer commands are run from
This template provides a solid foundation. Here are some areas you might want to explore for further customization or more advanced setups:
-
Database and File Backups:
- Implement a robust and regular backup strategy for:
- MySQL Database: Use tools like
mysqldump(can be run viadocker compose exec db_prod mysqldump ...) scheduled withcron, or use DigitalOcean's snapshot/backup features for the Droplet. Store backups securely, preferably off-server. wp-content/uploadsDirectory: This directory contains user-uploaded media. It should be backed up regularly. If it's not part of your Git repository,rsyncor other backup tools can be used.
- MySQL Database: Use tools like
- Consider backup frequency, retention policies, and testing your restore process.
- Implement a robust and regular backup strategy for:
-
Advanced Frontend Build Workflows (Vite/Webpack):
- If your themes or plugins require more complex JavaScript and CSS processing (e.g., SCSS/SASS, TypeScript, PostCSS, JS module bundling, React/Vue components), you can integrate tools like Vite or Webpack.
- This is typically done by setting up a Node.js environment within each specific theme or plugin directory that needs it (i.e., its own
package.json,vite.config.jsorwebpack.config.js). - The build tool's development server (e.g., Vite dev server on
localhost:5173orwebpack-dev-serveronlocalhost:8080) would serve assets with HMR. - Your WordPress theme/plugin PHP code would need to be adapted to enqueue assets from the dev server during development and from a
dist/build directory (using a manifest file) in production. - BrowserSync (run from the project root) can still be used alongside these tools to handle full-page reloads for PHP file changes, while Vite/Webpack handles HMR for the assets they manage.
-
More Complex CI/CD Scenarios:
- Building Frontend Assets in CI: If you use Vite/Webpack, you can add a step to your GitHub Actions workflow (
deploy.yml) to runnpm install && npm run buildfor your theme/plugin. The CI would then deploy the compiled static assets instead of the source files. - Automated Testing: Integrate PHPUnit tests or other automated tests into your CI pipeline.
- Staging/Production Environments: Create separate workflows or use different branches/secrets for deploying to a staging environment before deploying to production.
- Docker Image Registry: For more robust deployments, your CI/CD pipeline could build a custom Docker image (containing WordPress, your
wp-contentwith Composer dependencies and built frontend assets) and push it to a Docker registry (like Docker Hub or GitHub Container Registry - GHCR). The server would then pull this pre-built image instead of just syncing files and runningcomposer install.
- Building Frontend Assets in CI: If you use Vite/Webpack, you can add a step to your GitHub Actions workflow (
-
Optimizing Docker Images:
- Use multi-stage builds in your
Dockerfileto keep the final image size smaller (e.g., use a build stage for Composer install or asset compilation, then copy only necessary artifacts to the final production image). - Ensure your production WordPress image doesn't contain unnecessary development tools.
- Use multi-stage builds in your
Security is crucial for any web application. Here are some important reminders for your WordPress project:
-
Strong Credentials:
- Always use strong, unique passwords for your MySQL database users (root and WordPress user), WordPress admin accounts, SSH access, and any other services.
- Store sensitive credentials like production database passwords and API keys as encrypted GitHub Secrets for your CI/CD workflow. Do not commit them directly to your repository.
- Regularly rotate passwords and API keys where appropriate.
-
WordPress Security Keys and Salts:
- Ensure that your
wp-config.php(or the.envfile that populates it on the server) uses unique and complex WordPress security keys and salts. Generate these from https://api.wordpress.org/secret-key/1.1/salt/ for your production environment.
- Ensure that your
-
Keep Software Updated:
- Regularly update WordPress core to the latest version.
- Keep all your themes and plugins updated to their latest versions to patch known vulnerabilities.
- Keep your server's operating system and installed packages (like Docker, PHP, MySQL if run on host) updated with security patches.
-
Secure Server Configuration:
- Firewall (UFW): Ensure your server's firewall is active and only allows traffic on necessary ports (SSH, HTTP, HTTPS). Restrict access to sensitive ports (like phpMyAdmin's custom port) to specific trusted IP addresses if possible.
- SSH Security:
- Use SSH key-based authentication instead of passwords for server access.
- Disable root login via SSH if you have a dedicated sudo user for administration (
PermitRootLogin noinsshd_config). - Consider changing the default SSH port (though this is more security through obscurity).
- Use tools like
fail2banto monitor SSH logs and block IPs with suspicious activity.
- User Privileges: Avoid working as
rootcontinuously on your server for day-to-day management. Use a dedicated user withsudoprivileges for administrative tasks. (Note: This template's CI/CD was configured to userootfor deployment as per earlier discussions; for enhanced security in a real production setup, consider a dedicated non-root deploy user with specific, limited permissions).
-
HTTPS (SSL/TLS):
- Always serve your live WordPress site over HTTPS.
- Use a reverse proxy like Nginx to handle SSL termination. Obtain free SSL certificates from Let's Encrypt using Certbot.
- Configure HSTS (HTTP Strict Transport Security) headers.
-
phpMyAdmin Security (if deployed to production):
- If you deploy phpMyAdmin to your production server, ensure it is heavily secured:
- Access it via HTTPS.
- Place it on a non-standard, hard-to-guess URL path.
- Protect access with strong HTTP Basic Authentication via your reverse proxy (Nginx/Apache).
- Restrict access to its port via the firewall to only trusted IP addresses.
- Consider removing it from the production server when not actively needed.
- If you deploy phpMyAdmin to your production server, ensure it is heavily secured:
-
Regular Backups:
- Implement a strategy for regular automated backups of your WordPress database and files (especially
wp-content/uploads). - Store backups securely, preferably in an off-server location.
- Periodically test your backup restoration process.
- Implement a strategy for regular automated backups of your WordPress database and files (especially
-
Monitor Logs:
- Regularly check server logs, WordPress logs (if debug logging is enabled temporarily for troubleshooting), and Docker container logs for any suspicious activity or errors.
-
WordPress Specific Security:
- Use security plugins (e.g., Wordfence, Sucuri) if appropriate for your needs, but understand their resource impact.
- Limit login attempts.
- Use Two-Factor Authentication (2FA) for WordPress admin accounts.
- Be mindful of the themes and plugins you install; use reputable sources.
This project is licensed under the MIT License - see the LICENSE file for details.