I have NixOS 22.11 set up as on a local workstation. I'm looking for a way to create a customizable and flexible local environment for Wordpress development using Nix. I want the solution to be "ephemeral" (just drop-in config to any directory & run) and not modify my main system configuration (it uses flakes). I'm aware of tool like: arion, but it uses docker-compose
under the hood.
I need a solution that is equivalent to docker-compose
in terms of easy setup of multiple, local Wordpress sites. However, it must use native NixOS configuration and Nix language to setup the needed services. It should be independent on docker
or podman
, but it should work with local treafik
subdomains.
Currently, I'm using traefik
routing with local subdomains. It is configured with dnsmasq
and locally-trusted certificates using mkcert
, as described in this article
Here is my docker-compose.yaml
version: '3'
services:
mariadb:
image: bitnami/mariadb:latest
volumes:
- 'mariadb_data:/bitnami/mariadb'
restart: always
environment:
- MARIADB_ROOT_PASSWORD=wordpress
- MARIADB_DATABASE=wordpress
- MARIADB_USER=wordpress
- MARIADB_PASSWORD=wordpress
networks:
- web
healthcheck:
test: [ 'CMD', '/opt/bitnami/scripts/mariadb/healthcheck.sh' ]
interval: 15s
timeout: 5s
retries: 6
wordpress:
image: wordpress:latest
# command: -H unix:///var/run/docker.sock
ports:
- 8080:80
expose:
- 8080
security_opt:
- no-new-privileges:true
networks:
- web
depends_on:
- mariadb
restart: always
environment:
- WORDPRESS_DB_HOST=mariadb
- WORDPRESS_DB_USER=wordpress
- WORDPRESS_DB_PASSWORD=wordpress
- WORDPRESS_DB_NAME=wordpress
labels:
- traefik.enable=true
- traefik.docker.network=web
- traefik.http.routers.wp-http.entrypoints=web
- traefik.http.routers.wp-http.rule=Host(`wp.docker.localdev`)
- traefik.http.routers.wp-http.middlewares=wp-https
- traefik.http.middlewares.wp-https.redirectscheme.scheme=https
- traefik.http.routers.wp-https.entrypoints=websecure
- traefik.http.routers.wp-https.rule=Host(`wp.docker.localdev`)
- traefik.http.routers.wp-https.tls=true
# - traefik.http.services.wp.loadbalancer.server.port=8080
volumes:
- ./wp-content:/var/www/html/wp-content
volumes:
db_data:
mariadb_data:
driver: local
networks:
web:
external: true
Is it possible and how can I achieve this? I'm looking for more for a guidance: something like a code skeleton or even general tips are welcome.
I found the solution that meets my needs. The below config works for me, but it requires some fine-tuning.
I used extra-container, which can run declarative NixOS containers like imperative containers, without system rebuilds, from any folder.
The services used:
.docker.localdev
might be misleading, I don't use docker here. I just had this name pre-configured in my system.wp.nix
{ pkgs, lib, config, ... }:
let
app = "wpdemo";
socket = "/run/phpfpm/${app}.sock";
domain = "localhost";
in
{
containers.wp = {
config = {
networking.firewall.enable = false;
security.acme.defaults.email = "admin@docker.localdev";
networking.firewall.allowedTCPPorts = [ 80 82 ];
services.traefik = {
enable = true;
staticConfigOptions = {
providers.docker = {
exposedByDefault = false;
};
entryPoints.web.address = ":80";
};
dynamicConfigOptions = {
http.routers.wp = {
rule = "Host(`wp.docker.localdev`)";
entryPoints = [ "web" ];
service = "wp-service";
};
http.services.wp-service.loadBalancer.server.port = 80;
};
};
services.phpfpm.pools.${app} = {
user = app;
settings = {
"listen.owner" = "nginx";
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.max_requests" = 500;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 5;
"php_admin_value[error_log]" = "stderr";
"php_admin_flag[log_errors]" = true;
"catch_workers_output" = true;
};
phpEnv."PATH" = lib.makeBinPath [ pkgs.php ];
};
services.mysql = {
enable = true;
package = pkgs.mariadb;
settings = {
"mysqld" = {
"port" = 3308;
};
};
initialScript =
pkgs.writeText "initial-script" ''
CREATE DATABASE IF NOT EXISTS wordpress;
CREATE USER IF NOT EXISTS 'admin'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'admin'@'localhost';
'';
ensureDatabases = [
"wordpress"
];
ensureUsers = [
{
name = "admin";
ensurePermissions = {
"admin.*" = "ALL PRIVILEGES";
"*.*" = "ALL PRIVILEGES";
};
}
];
};
systemd.services.wordpress.serviceConfig = {
ProtectSystem = lib.mkForce false;
ProtectHome = lib.mkForce false;
ReadWritePaths = [ "/var" "/home" "/home/www" ];
};
systemd.services.wpsetup = {
path = with pkgs; [ coreutils wget gzip curl unzip rsync ];
wantedBy = [ "multi-user.target" ];
script = ''
mkdir -p /var/www/wpdemo
# WordPress
cd /var/www/wpdemo
curl -L https://wordpress.org/latest.zip -o wordpress.zip
unzip wordpress.zip -d ./tmp
mv ./tmp/*/* .
rm -rf tmp
rm ./wordpress.zip
# Storefront theme
cd /var/www/wpdemo/wp-content/themes
curl -L https://downloads.wordpress.org/theme/storefront.4.2.0.zip -o storefront.zip
unzip storefront.zip
rm storefront.zip
# WooCommerce plugin
cd /var/www/wpdemo/wp-content/plugins
curl -L https://downloads.wordpress.org/plugin/woocommerce.7.4.1.zip -o woocommerce.zip
unzip woocommerce.zip
rm woocommerce.zip
chmod 777 -R /var/www/wpdemo
'';
serviceConfig = {
ProtectSystem = lib.mkForce false;
ProtectHome = lib.mkForce false;
ReadWritePaths = [ "/var" "/home" "/home/wpdemo" "/home/wpdemo/www" ];
};
};
systemd.services.ngnix.serviceConfig = {
ProtectSystem = lib.mkForce false;
ProtectHome = lib.mkForce false;
ReadWritePaths = [ "/var" "/home" "/home/wpdemo" "/home/wpdemo/www" ];
};
services.nginx = {
enable = true;
virtualHosts.${domain} = {
listen = [{
addr = "127.0.0.1";
port = 80;
}];
serverName = "wp.docker.localdev";
locations."/" = {
root = "/var/www/wpdemo";
extraConfig = ''
access_log off;
charset utf-8;
etag off;
index index.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:${socket};
include ${pkgs.nginx}/conf/fastcgi_params;
include ${pkgs.nginx}/conf/fastcgi.conf;
}
'';
};
};
};
users.mutableUsers = true;
users.users.${app} = {
isSystemUser = true;
createHome = true;
home = "/home/wpdemo";
group = app;
};
users.groups.${app} = { };
};
# bindMounts = {
# "/var/www/wpdemo/wp-content/themes" = {
# hostPath = "/var/www/wpdemo/wp-content/themes/";
# isReadOnly = false;
# };
# };
};
}
I run it with the command:
sudo extra-container create --start <<EOF
$(cat wp.nix)
EOF