nixnixosnix-flake

How to set up local Wordpress development environment using Nix without modifying flakes-based system configuration?


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.


Solution

  • 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:

    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