Hi, I’ve been dabbling in the NixOS world for some months now and am at the point where I’ve got my day to day setup working okay.
Part of what I do is work on WordPress sites. I used to have Apache, PHP, and MariaDB installed on my Ubuntu (now NixOS) machine. So there are directories under /var/www with full WordPress installations. I would love to be able to work in these directories and be able to manage the files myself. I mostly do plugin development there… but for several different sites.
I am currently trying to go the route of setting up a flake.nix file in the www directory and then running nix develop in the pertinent sub-directory to get everything going for that site. Nixfoo is not strong enough to get this to work (should read loooots more) so I’d love to know how any of you would approach this. Quitting is not on the table! 
The bare basics I need is PHP, and MariaDB. Not sure I want the WordPress package, since I think it may constrain my ability to do dev work (edit files and plugins). I have ran PHP -S in the past with good enough success, so Nginx or Apache are not a huge deal, but maybe a nice addition.
I’d share the four different files I’m tying to build, but I don’t want to get banned from this forum 
Hi, I believe the easier way for this kind of dev stack is using tools like devenv or flox. I’ve only tried devenv yet, it provides php and services like mariadb and nginx, and you can start your stack using a devenv up
command, just like docker compose.
You might want to dig into forums’ posts a bit. Some ppl have shared working configurations for NixOS already:
https://discourse.nixos.org/search?q=wordpress
I haven’t really found any examples on the web, or the forums, that would be for local development environments where I would be able to edit files/plugins at will. What I have found are examples of production configurations that would be better for serving on the web. I have cobbled together things from those here.
So far I’m feeling pretty close and learning a whole lot in the process, which is one of the reasons I’m trying to build this flake.
# flake.nix
{
description = "WordPress dev environment!";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
};
outputs = { self, nixpkgs, ... }@inputs:
let
flakeName = "WordPress Dev";
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
# Set default values for the command-line arguments
URL = "localhost:8089";
PHP_VERSION = "8.1";
WORDPRESS_VERSION = "5.9";
DOWNLOAD_WORDPRESS = false;
wpConfig = builtins.readFile ./wp-config.php;
db_name = builtins.head (builtins.match ".*define\\([[:space:]]?['\"]DB_NAME['\"], ['\"]([^']+)['\"][[:space:]]?\\);.*" "${wpConfig}");
db_user = builtins.head (builtins.match ".*define\\([[:space:]]?['\"]DB_USER['\"], ['\"]([^']+)['\"][[:space:]]?\\);.*" "${wpConfig}");
db_password = builtins.head (builtins.match ".*define\\([[:space:]]?['\"]DB_PASSWORD['\"], ['\"]([^']+)['\"][[:space:]]?\\);.*" "${wpConfig}");
host = "localhost";
# Create a SQLite database file
# Not working, may look into this later... Using a full MySQL db for now.
# sqlite_db = nixpkgs.fetchurl {
# url = "https://raw.githubusercontent.com/aaemnnosttv/wp-sqlite-db/master/src/db.php";
# };
# sqliteDbFile = builtins.storeFile "${wordpress}/wp-content/db.php" sqlite_db;
# Download WordPress if the flag is set
wordpress = if DOWNLOAD_WORDPRESS then
(import (fetchTarball {
url = "https://wordpress.org/wordpress-${WORDPRESS_VERSION}.tar.gz";
# sha256 = nix-prefetch-url --print-sha256 "https://wordpress.org/wordpress-${WORDPRESS_VERSION}.tar.gz";
}))./.
else
./.;
services.mysql = {
enable = true;
dataDir = "./db";
package = pkgs.mariadb_110;
initialScript =
let
# https://sourcegraph.com/github.com/vlktomas/nix-examples/-/blob/web/Laravel/laravel/cd.nix?L112&subtree=true
# databasePassword = lib.removePrefix "DB_PASSWORD=" (lib.fileContents databasePasswordFile);
databasePassword = db_password;
in
pkgs.writeText "initial-script" ''
CREATE DATABASE IF NOT EXISTS `${db_name}`;
CREATE USER IF NOT EXISTS '${db_user}'@'${host}' IDENTIFIED WITH ${db_password};
ALTER USER '${db_user}'@'${host}' IDENTIFIED BY '${databasePassword}';
GRANT ALL PRIVILEGES ON `${db_name}`.* TO '${db_user}'@'${host}';
'';
ensureDatabases = [
db_name
];
ensureUsers = [
{
name = db_user;
ensurePermissions = {
"${db_user}.*" = "ALL PRIVILEGES";
"*.*" = "ALL PRIVILEGES";
};
}
];
};
# Initialize the SQLite database
# initSqliteDb = ''
# sqlite3 ${sqliteDbFile} < ${wordpress}/wp-admin/includes/schema.php
# '';
# Start the PHP development server
startPhpServer = ''
php -S ${URL} -t ${wordpress} &
'';
# Start the SQLite database server
# startSqliteServer = ''
# sqlite3 ${sqliteDbFile} &
# '';
in {
devShells.x86_64-linux.default = pkgs.mkShell {
name = "wordpress-dev";
# Define the dependencies
nativeBuildInputs = with pkgs; [
mariadb_110
php
php82Extensions.xdebug
# sqlite
wp-cli
];
# Parse command-line arguments
# May try this later, not working...
# args = builtins.removeAttrs (builtins.parseArgs {
# options = {
# url = opts: URL = opts.arg;
# php-version = opts: PHP_VERSION = opts.arg;
# wordpress-version = opts: WORDPRESS_VERSION = opts.arg;
# download-wordpress = opts: DOWNLOAD_WORDPRESS = true;
# };
# }) ["_"];
# Start the services when entering the shell
shellHook = ''
PS1="[${flakeName}] $PS1"
${startPhpServer}
echo "WordPress development environment started. Accessible at http://${URL}"
'';
};
};
}
However I’m getting an error on the browser:
And this in the command line:
[sergio@samara:~/www/test]$ nix develop
WordPress development environment started. Accessible at http://localhost:8089
[WordPress Dev]
[sergio@samara:~/www/test]$ [Sat Jun 22 12:29:17 2024] Failed to listen on localhost:8089 (reason: Address already in use)
[1]+ Exit 1 php -S localhost:8089 -t /nix/store/cax9pphk7ir9zpabzm9z4fz3r405yliw-2x3n2ji5y6yr7h6rh2ksvli8kcwsnqbz-source
[WordPress Dev]
I’m pretty sure I’m missing something with the database creation also the store pat seems different on the WordPress error page and the command line. Seems this may be running twice?
Still working on it… Any ideas or advice would be appreciated.
There’s a lot wrong with the flake
-
services.mysql
is never used. Even if it was, such options are used in NixOS configurations, not development shells. You probably expect it to start a mysql server, but it will not.
-
builtins.parseArgs
is not a thing as far as I know (is this an LLM hallucination?). I don’t think flakes even have any way to take arguments in the command line.
-
builtins.storeFile
is not a thing either I think?
- You probably need something like
php82Extensions.mysqli
for WordPress to be able to connect to mysql (if it were running). And you might need to use it in php.buildEnv
as described in the wiki.
I would suggest starting with a smaller example without a bunch of unused and invalid code.
Honestly, I haven’t used devenv or flox as recommended by others, but they seem like they might be more fitting for this purpose. Nix shells do not provide any service management (as NixOS configuration does), but e.g. a combination of services and processes in devenv might let you achieve a dev environment with pretty high level code. With those, you might be able to just services.mysql.enable = true;
, define your own process to run php -S
, et voila.
I think flakes is great for packaging your app and even providing a nixosModule to run it, but to run a dev stack with multiple processes and a database devenv makes it super easy. I just tried with a fresh wordpress install and got it working with this simple devenv.nix
:
{ pkgs, ... }:
{
packages = with pkgs; [ git wp-cli ];
services.mysql.enable = true;
services.mysql.package = pkgs.mariadb;
services.mysql.initialDatabases = [{ name = "wordpress"; }];
services.mysql.ensureUsers = [{
name = "wordpress";
password = "wordpress";
ensurePermissions = { "wordpress.*" = "ALL PRIVILEGES"; };
}];
languages.javascript.enable = true;
languages.php.enable = true;
process.before = "npm install";
processes.wordpress.exec = "wp server";
processes.wordpress-assets.exec = "npm run dev";
}
When running devenv up
it spawns all the processes using process compose and you get this TUI:
Tip: with this setup when configuring WP you need to use 127.0.0.1
for DB host (not localhost
). Also if you want to use phpfm this blog post should help.
3 Likes
Great points all around!
- didn’t know that (good to know).
- yup, was stuck with something and asked the robots… shoulda known better
- same…
- I’ll look into that, but it seems like I won’t be able to start the database server anyway?
So I won’t be able to define and start a service from a flake, this means I’ll need to (at the very least) configure services in my configuration file.
Just setting this up, I’m learning a whole lot about how Nix works. I’m going to continue down this path for a bit. Assuming it actually is possible… 
Why not using docker
and “translate” the setup with nix
available docker
tools?
https://nixos.wiki/wiki/Docker
Thanks for the info. Trying this now but… This may seem like a very basic question, but I’m getting complains of a package.json missing and I have no clue what should be in this file.
And if I comment out the lines 18 and 20 it connects to the db… but I now don’t know how I can access wp-cli…
I swear I’ve been working on this kind of thing for years 
you’re getting some nice feedback in this thread on how to change your workflow, though maybe at this point you don’t want to change your workflow quite yet and you just want your LAMP
stack back and working in a way that is simple for you to understand given your past experiences… to become productive in the way you used to be on ubuntu, but on NixOS instead?
while personally i would recommend devenv for php
development for wordpress
i can understand how that might be a bit much at that point… and if so, just say so and i can write up a quick and easy LAMP
on NixOS configuration which will be familiar to you, given what you described in your initial post
I very much appreciate your offer! Yes, that LAMP configuration would be great to get me coding again, while I sort this out further. Running into a bit of a time crunch with some projects and I don’t really want to spin up a vm for this.
The goal is definitely to dive more into the more use of flakes and devenv for my day-to-day workflows. But it may be a dash premature… Even if I am loving all the new options. There is lots of reading and trial/error ahead of me.
you can pop the following config in something like lamp.nix
and then include in your system configuration:
{ config, pkgs, lib, ... }:
let
# php 8.1 is the easiest option - if you need php 7.x then we can discuss https://github.com/fossar/nix-phps/ as an option
php' = pkgs.php81.buildEnv {
# any customizations to your `php.ini` go here
extraConfig = ''
memory_limit = 1024M
'';
};
in
{
networking.hosts = {
# convenient if you're going to work on multiple sites
"127.0.0.1" = [ "example.org" ];
};
services.mysql.enable = true;
services.mysql.package = pkgs.mariadb;
services.mysql.ensureDatabases = [
# list a database for every site you want and they will be automatically created
"example"
];
services.mysql.ensureUsers = [
# NOTE: it is important that `name` matches your `$USER` name, this allows us to avoid password authentication
{ name = "aaron";
ensurePermissions = {
"*.*" = "ALL PRIVILEGES";
};
}
];
services.phpfpm.pools."example.org" = {
user = "aaron";
group = "users";
phpPackage = php';
settings = {
"listen.owner" = config.services.caddy.user;
"listen.group" = config.services.caddy.group;
"pm" = "dynamic";
"pm.max_children" = 5;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 1;
"pm.max_spare_servers" = 5;
};
};
services.caddy.enable = true;
# we'll keep it simple and stick to plain http for now, though caddy supports https relatively easily
services.caddy.virtualHosts."http://example.org:80".extraConfig = ''
root * /var/www/example.org
php_fastcgi unix/${config.services.phpfpm.pools."example.org".socket}
file_server
'';
# automatically create a directory for each site you will work on with appropriate ownership+permissions
systemd.tmpfiles.rules = [
"d /var/www/example.org 0755 aaron users"
];
}
- rebuild your system
- unzip wordpress into
/var/www/example.org
such that index.php
, wp-cron.php
, etc… are in that directory
- head to http://example.org in your browser
- fill out wordpress installation web form… see screen capture for database details
to be clear i rebuilt my system against the exact configuration i provided you, it wasn’t a “for example” snippet of config, it will work exactly like that if your user account is aaron
… so just search+replace aaron
with your system username and you should be up and running 
let me know if you have any issues
2 Likes
Maybe a bit out of topic, but any pros / cons using devenv
?
For personal experience, 1 stack for all tend to be quite messy on the long term for WP, especially when dealing with multiple sites and instances.
How would you manage
- compatibility testing for both PHP (e.g
7.4
, 8.1
, 8.2
, etc), WP (5.*
, 6.*
, etc), db
, etc
- more “modern” approach with
wp-env
for development, nginx
to server, etc
- sites “encapsulation” to respect clients privacy policies
I’m production with NixOS you can run as many different versions of php
side by side for each site as you like, you can run each site entirely sandboxes from each other as separate users with restrictive permissions, and separate database with restrictive permissions as well
NixOS makes for an incredibly powerful php
experience both in development and production
1 Like
Thank you for putting that together! I really appreciate the help. After a couple of tweaks, it seems to be working fine. Had to move my www directory to /var/www, it didn’t work if I changed the path…
One main thing remains for me, that is to get xdebug working from configuragion.nix
. Instead I have to also add it to lamp.nix
.
#configuration.nix
environment.systemPackages = with pkgs; [
devenv
direnv
php83Packages.php-codesniffer
(php83.buildEnv {
extensions = ({ enabled, all }: enabled ++ (with all; [
xdebug
imagick
]));
extraConfig = ''
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.idekey = gdbp
'';
})
spacevim
wget
wmutils-core
wp-cli
];
#lamp.nix
php' = pkgs.php83.buildEnv {
extensions = ({ enabled, all }: enabled ++ (with all; [
xdebug
imagick
]));
# any customizations to your `php.ini` go here
extraConfig = ''
memory_limit = 1024M
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.idekey = gdbp
'';
Looking online for a while now and continuing to dig into it. If you know of how, it would be great! I’ll share what I’ve done soon, as I’m adding some “extra” things. Mainly wanting to import existing sites/dbs, which I managed. 
was it under /home
? the phpfpm
module has some systemd
hardening which blocks that - if it is a real hassle we can undo that, let me know
maybe this can help you:
php' = pkgs.php81.buildEnv {
extensions = { enabled, all }: with all; enabled ++ [ xdebug ];
extraConfig = ''
memory_limit = 1024M
xdebug.show_error_trace = 1
xdebug.show_local_vars = 1
xdebug.remote_enable = 1
xdebug.remote_autostart = 1
'';
};
looking forward to it
I was editing my last reply with an update as you replied. Got it working, but had a question still…
I figures as much, no problem at all. I just moved the files…
Back again… I am trying to see if I can have a single, say, php.nix
file (module?) and call it from lamp.nix
and configuration.nix
. But that’s just icing on the cake. I’ve had a productive day developing with this working WordPress setup. So, thank you!!!
I am having one issue that relates to the site paths… I moved most of my files to a server in my LAN and connected through NFS. It’s working fine, but now the sites aren’t loading. I’m assuming it’s the same systemd
hardening issue you mentioned? The NFS mount is at /run/media/sergio/vault/www/example.org
. I did try to find the solution online, but nothing yet…
sorry, can you provide context, i don’t follow what you mean?
i would imagine if you undo these lines your issue would be resolved:
something like this:
systemd.services."phpfpm-example.org".serviceConfig = {
PrivateDevices = lib.mkForce false;
PrivateTmp = lib.mkForce false;
ProtectSystem = lib.mkForce "off";
ProtectHome = lib.mkForce false;
};
This gives me the same 403 error when going to the site. Here is what I have for that part:
services.phpfpm.pools."example.org" = {
user = "sergio";
group = "users";
phpPackage = php';
settings = {
"listen.owner" = config.services.caddy.user;
"listen.group" = config.services.caddy.group;
"pm" = "dynamic";
"pm.max_children" = 5;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 1;
"pm.max_spare_servers" = 5;
};
};
services.caddy.enable = true;
# we'll keep it simple and stick to plain http for now, though caddy supports https relatively easily
services.caddy.virtualHosts."http://example.org:80".extraConfig = ''
root * /run/media/sergio/vault/www/example.org
php_fastcgi unix/${config.services.phpfpm.pools."example.org".socket}
file_server
'';
# automatically create a directory for each site you will work on with appropriate ownership+permissions
systemd.tmpfiles.rules = [
"d /run/media/sergio/vault/www/example.org 0755 sergio users"
];
systemd.services."phpfpm-example.org".serviceConfig = {
PrivateDevices = lib.mkForce false;
PrivateTmp = lib.mkForce false;
ProtectSystem = lib.mkForce "off";
ProtectHome = lib.mkForce false;
};
}
sorry, can you provide context, i don’t follow what you mean?
The other thing I was meaning is that I am declaring(?) PHP and it’s extraConfigs
twice. Was looking to have it in one, maybe, php.nix
config and import it… Just a learning bit, not critical at all.
#configuration.nix
environment.systemPackages = with pkgs; [
devenv
direnv
php83Packages.php-codesniffer
(php83.buildEnv {
extensions = ({ enabled, all }: enabled ++ (with all; [
xdebug
imagick
]));
extraConfig = ''
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.idekey = gdbp
'';
})
spacevim
wget
wmutils-core
wp-cli
];
#lamp.nix
php' = pkgs.php83.buildEnv {
extensions = ({ enabled, all }: enabled ++ (with all; [
xdebug
imagick
]));
# any customizations to your `php.ini` go here
extraConfig = ''
memory_limit = 1024M
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.idekey = gdbp
'';