{
  "authors": [
    {
      "name": "Ostap Brehin",
      "url": "https://ostapbrehin.com/"
    }
  ],
  "description": "Various notes and posts by Ostap Brehin",
  "feed_url": "https://ostapbrehin.com/feed.json",
  "home_page_url": "https://ostapbrehin.com/",
  "items": [
    {
      "content_html": "\u003cp\u003eFL runs surprisingly well under Wine. It\u0026#39;s also a good idea to keep it in a sandboxed environment so it can\u0026#39;t access the rest of your system.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/M0n7y5/pipeasio/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ePipeASIO\u003c/a\u003e is a brilliant alternative to \u003ca href=\"https://github.com/wineasio/wineasio/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWineASIO\u003c/a\u003e which skips the Jack compatibility layer altogether.\u003c/p\u003e\n\u003ch2 id=\"build-pipeasio-on-fedora\"\u003e\u003ca class=\"heading-anchor\" href=\"#build-pipeasio-on-fedora\"\u003eBuild PipeASIO on Fedora\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eFirst, the building blocks:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo dnf install wine cmake gcc-c++ wine-devel pipewire-devel qt6-qtbase-devel firejail\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNow you\u0026#39;re ready to build PipeASIO:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003egit clone https://github.com/M0n7y5/pipeasio.git\ncd pipeasio\ncmake -B build -DCMAKE_BUILD_TYPE=Release\ncmake --build build\ncmake --install build --prefix \u0026#34;$HOME/.local\u0026#34;\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"initialize-wine-prefix\"\u003e\u003ca class=\"heading-anchor\" href=\"#initialize-wine-prefix\"\u003eInitialize Wine Prefix\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eCreate an isolated container for FL Studio to live in:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emkdir -p ~/isolated-apps/flstudio/wineprefix\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efirejail \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --private\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"s2\"\u003e/isolated-apps/flstudio\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --whitelist\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"s2\"\u003e/isolated-apps/flstudio\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --env\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003eWINEPREFIX\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"s2\"\u003e/wineprefix\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --env\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003eWINEARCH\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003ewin64 \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  wine wineboot --init\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eVerify the structure exists:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003els ~/isolated-apps/flstudio/wineprefix\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou should see \u003ccode\u003edrive_c\u003c/code\u003e, \u003ccode\u003esystem.reg\u003c/code\u003e, and related files.\u003c/p\u003e\n\u003ch2 id=\"install-fl-studio\"\u003e\u003ca class=\"heading-anchor\" href=\"#install-fl-studio\"\u003eInstall FL Studio\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eKeep the installer inside the sandbox. This way nothing else needs whitelisting.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecp /path/to/FLStudio_installer.exe ~/isolated-apps/flstudio/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eRun the installer. Network is open here - it needs to check licensing:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efirejail \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --private\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"s2\"\u003e/isolated-apps/flstudio\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --whitelist\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"s2\"\u003e/isolated-apps/flstudio\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --env\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003eWINEPREFIX\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"s2\"\u003e/wineprefix\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  wine \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"s2\"\u003e/flstudio_win64_25.2.5.5319.exe\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe command may hang after the installer finishes. Stop it with Ctrl+C.\u003c/p\u003e\n\u003ch2 id=\"install-pipeasio\"\u003e\u003ca class=\"heading-anchor\" href=\"#install-pipeasio\"\u003eInstall PipeAsio\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eOn Fedora with Wine-Staging 11, there\u0026#39;s a naming mismatch that breaks at runtime.\u003c/p\u003e\n\u003cp\u003eCopy the built DLLs into Wine\u0026#39;s system path:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo cp ~/.local/lib/wine/x86_64-windows/pipeasio64.dll /usr/lib64/wine-wow64/wine/x86_64-windows/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo cp ~/.local/lib/wine/x86_64-unix/pipeasio64.dll.so /usr/lib64/wine-wow64/wine/x86_64-unix/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNow the critical part: create a symlink. Wine\u0026#39;s builtin-DLL loader looks for \u003ccode\u003epipeasio.dll\u003c/code\u003e internally, but the actual file is named \u003ccode\u003epipeasio64.dll\u003c/code\u003e. Without this, you get \u0026ldquo;couldn\u0026#39;t load in-process dll\u0026rdquo; at runtime - the registration succeeds, but the load fails.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo ln -sf /usr/lib64/wine-wow64/wine/x86_64-unix/pipeasio64.dll.so \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  /usr/lib64/wine-wow64/wine/x86_64-unix/pipeasio.dll.so\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis is a quirk in how pipeasio\u0026#39;s CMake build names its output versus what it bakes into the PE header.\u003c/p\u003e\n\u003cp\u003eRegister into the prefix:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eexport\u003c/span\u003e \u003cspan class=\"nv\"\u003eWINEPREFIX\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"s2\"\u003e/isolated-apps/flstudio/wineprefix\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e~/.local/bin/pipeasio-register\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ewineboot -u\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eCheck it worked:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efind ~/isolated-apps/flstudio/wineprefix -iname \u003cspan class=\"s2\"\u003e\u0026#34;*pipeasio*\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou should see the registered DLL entries.\u003c/p\u003e\n\u003ch2 id=\"daily-launch\"\u003e\u003ca class=\"heading-anchor\" href=\"#daily-launch\"\u003eDaily Launch\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eCreate a script that runs FL Studio inside firejail:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecat \u0026gt; ~/isolated-apps/flstudio/run.sh \u003cspan class=\"s\"\u003e\u0026lt;\u0026lt; \u0026#39;EOF\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e#!/bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003efirejail \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --noprofile \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --net=none \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --private=\u0026#34;$HOME/isolated-apps/flstudio\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --whitelist=\u0026#34;$HOME/isolated-apps/flstudio\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --whitelist=/run/user/1000 \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --whitelist=/tmp/.X11-unix \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --env=WINEPREFIX=\u0026#34;$HOME/wineprefix\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --env=DISPLAY=\u0026#34;$DISPLAY\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --env=WAYLAND_DISPLAY=\u0026#34;$WAYLAND_DISPLAY\u0026#34; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --env=XDG_RUNTIME_DIR=/run/user/1000 \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --caps.drop=all \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --seccomp \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --private-dev \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --nonewprivs \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  --noroot \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e  wine \u0026#34;C:\\\\Program Files\\\\Image-Line\\\\FL Studio 2025\\\\FL64.exe\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eEOF\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echmod +x ~/isolated-apps/flstudio/run.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eRun it:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e~/isolated-apps/flstudio/run.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eOpen Audio Settings in FL Studio. PipeAsio should appear as an ASIO driver. If you prefer to use FL Cloud or other network features, drop the \u003ccode\u003e--net=none\u003c/code\u003e flag.\u003c/p\u003e\n",
      "date_modified": "2026-10-01T04:57:20+01:00",
      "date_published": "2026-07-02T00:00:00Z",
      "id": "https://ostapbrehin.com/fl-studio-fedora-pipeasio/",
      "summary": "FL runs surprisingly well under Wine. It's also a good idea to keep it in a sandboxed environment so it can't access the rest of your system.\nPipeASIO is a brilliant alternative to WineASIO which skips the Jack compatibility layer altogether.\nBuild PipeASIO on FedoraFirst, the building blocks:\nsudo dnf install wine cmake gcc-c++ wine-devel pipewire-devel qt6-qtbase-devel firejail Now you're ready to build PipeASIO:\ngit clone https://github.com/M0n7y5/pipeasio.git cd pipeasio cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build cmake --install build --prefix \"$HOME/.local\" Initialize Wine PrefixCreate an isolated container for FL Studio to live in:\n",
      "tags": [
        "Fedora",
        "Linux",
        "Wine",
        "Audio"
      ],
      "title": "FL Studio on Fedora (with PipeASIO and sandboxing)",
      "url": "https://ostapbrehin.com/fl-studio-fedora-pipeasio/"
    },
    {
      "content_html": "\u003cp\u003e\u003ccode\u003e$request-\u0026gt;except()\u003c/code\u003e operates on the entire request, not just validated fields. This leaves you open to mass-assignment:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-php\" data-lang=\"php\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eUser\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"na\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s1\"\u003e\u0026#39;name\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003eimplode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39; \u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nv\"\u003e$request\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"na\"\u003efirst_name\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$request\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"na\"\u003elast_name\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"nv\"\u003e$request\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"na\"\u003eexcept\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;first_name\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;last_name\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnyone can slip a \u003ccode\u003escore\u003c/code\u003e or \u003ccode\u003eis_admin\u003c/code\u003e field through. Use \u003ccode\u003esafe()\u003c/code\u003e instead:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-php\" data-lang=\"php\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eUser\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"na\"\u003ecreate\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s1\"\u003e\u0026#39;name\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"nx\"\u003eimplode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39; \u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nv\"\u003e$request\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"na\"\u003efirst_name\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nv\"\u003e$request\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"na\"\u003elast_name\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"nv\"\u003e$request\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"na\"\u003esafe\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"na\"\u003eexcept\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;first_name\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;last_name\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003esafe()-\u0026gt;except()\u003c/code\u003e excludes from the validated set and not the raw request.\u003c/p\u003e\n",
      "date_modified": "2026-06-12T16:06:46+01:00",
      "date_published": "2025-12-28T12:20:18Z",
      "id": "https://ostapbrehin.com/laravel-request-safe-method/",
      "summary": "$request-\u003eexcept() operates on the entire request, not just validated fields. This leaves you open to mass-assignment:\nUser::create([ 'name' =\u003e implode(' ', [$request-\u003efirst_name, $request-\u003elast_name]), ...$request-\u003eexcept(['first_name', 'last_name']), ]); Anyone can slip a score or is_admin field through. Use safe() instead:\nUser::create([ 'name' =\u003e implode(' ', [$request-\u003efirst_name, $request-\u003elast_name]), ...$request-\u003esafe()-\u003eexcept(['first_name', 'last_name']), ]); safe()-\u003eexcept() excludes from the validated set and not the raw request.\n",
      "tags": [
        "Security",
        "Laravel",
        "Mass-Assignment"
      ],
      "title": "Laravel FormRequest safe() method is a must-know",
      "url": "https://ostapbrehin.com/laravel-request-safe-method/"
    },
    {
      "content_html": "\u003cp\u003eRecently, my client\u0026#39;s site was being DDoSed. It had two domains which had to interact with each other: the primary domain and the API. But as soon as I went for the “Under Attack” mode, the CORS stopped working.\u003c/p\u003e\n\u003cp\u003eI found a solution by adding a new \u0026ldquo;Snippet\u0026rdquo; under the \u0026ldquo;Rules\u0026rdquo; -\u0026gt; \u0026ldquo;Snippets\u0026rdquo; page:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eALLOWED\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003eSet\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"s2\"\u003e\u0026#34;https://example.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"s2\"\u003e\u0026#34;http://another.example.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eexport\u003c/span\u003e \u003cspan class=\"k\"\u003edefault\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003easync\u003c/span\u003e \u003cspan class=\"nx\"\u003efetch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eorigin\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eheaders\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Origin\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eallow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eALLOWED\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ehas\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eorigin\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e?\u003c/span\u003e \u003cspan class=\"nx\"\u003eorigin\u003c/span\u003e \u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;https://example.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ecors\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"s2\"\u003e\u0026#34;Access-Control-Allow-Origin\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003eallow\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"s2\"\u003e\u0026#34;Access-Control-Allow-Methods\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;GET, POST, PUT, PATCH, DELETE, OPTIONS\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"s2\"\u003e\u0026#34;Access-Control-Allow-Headers\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Content-Type, Authorization\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"s2\"\u003e\u0026#34;Access-Control-Allow-Credentials\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003emethod\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;OPTIONS\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003eResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003estatus\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eheaders\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003ecors\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eresp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kr\"\u003eawait\u003c/span\u003e \u003cspan class=\"nx\"\u003efetch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003eResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ev\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"k\"\u003eof\u003c/span\u003e \u003cspan class=\"nb\"\u003eObject\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eentries\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ecors\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eheaders\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ev\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003eNote: \u0026ldquo;Snippets\u0026rdquo; appears to be a paid feature available under Cloudflare\u0026#39;s 25$ tier.\u003c/p\u003e\n\u003c/blockquote\u003e\n",
      "date_modified": "2025-12-26T12:00:47Z",
      "date_published": "2025-12-19T01:12:08Z",
      "id": "https://ostapbrehin.com/cloudflare-under-attack-cors/",
      "summary": "Recently, my client's site was being DDoSed. It had two domains which had to interact with each other: the primary domain and the API. But as soon as I went for the “Under Attack” mode, the CORS stopped working.\nI found a solution by adding a new “Snippet” under the “Rules” -\u003e “Snippets” page:\nconst ALLOWED = new Set([ \"https://example.com\", \"http://another.example.com\", ]); export default { async fetch(request) { const origin = request.headers.get(\"Origin\"); const allow = ALLOWED.has(origin) ? origin : \"https://example.com\"; const cors = { \"Access-Control-Allow-Origin\": allow, \"Access-Control-Allow-Methods\": \"GET, POST, PUT, PATCH, DELETE, OPTIONS\", \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\", \"Access-Control-Allow-Credentials\": \"true\", }; if (request.method === \"OPTIONS\") { return new Response(null, { status: 200, headers: cors }); } const resp = await fetch(request); const out = new Response(resp.body, resp); for (const [k, v] of Object.entries(cors)) out.headers.set(k, v); return out; }, }; Note: “Snippets” appears to be a paid feature available under Cloudflare's 25$ tier.\n",
      "tags": [
        "Cloudflare"
      ],
      "title": "Cloudflare Under Attack CORS solution",
      "url": "https://ostapbrehin.com/cloudflare-under-attack-cors/"
    },
    {
      "content_html": "\u003cp\u003eOut-of-the-box, \u003ca href=\"https://github.com/inertiajs/inertia-phoenix\" target=\"_blank\" rel=\"noopener noreferrer\"\u003einertia-phoenix\u003c/a\u003e offers a great React installer:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emix archive.install hex igniter_new\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emix igniter.install inertia --client-framework\u003cspan class=\"o\"\u003e=\u003c/span\u003ereact --typescript\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNow, there are only 3 steps left to start using Preact in this project:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eInstall Preact\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enpm install preact \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e npm remove react-dom react\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col start=\"2\"\u003e\n\u003cli\u003eRegister paths in tsconfig:\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;compilerOptions\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;paths\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;react\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;./node_modules/preact/compat/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;react/jsx-runtime\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;./node_modules/preact/jsx-runtime\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;react-dom\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;./node_modules/preact/compat/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;react-dom/*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;./node_modules/preact/compat/*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col start=\"3\"\u003e\n\u003cli\u003eRegister aliases in in \u003ccode\u003econfig.exs\u003c/code\u003e esbuild flags\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-diff\" data-lang=\"diff\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gd\"\u003e- js/app.jsx ... \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"gi\"\u003e+ js/app.jsx --alias:react=preact/compat --alias:react-dom=preact/compat ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eNote:\u003c/strong\u003e while Inertia.js doesn\u0026#39;t provide a dedicated Preact client, the regular React client works well under Preact\u0026#39;s compatibility mode.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eHappy MPA+SPA-ing 🙂 ✌️\u003c/p\u003e\n",
      "date_modified": "2025-12-26T10:55:15Z",
      "date_published": "2025-12-17T09:40:09Z",
      "id": "https://ostapbrehin.com/preact-with-inertia-phoenix/",
      "summary": "Out-of-the-box, inertia-phoenix offers a great React installer:\nmix archive.install hex igniter_new mix igniter.install inertia --client-framework=react --typescript Now, there are only 3 steps left to start using Preact in this project:\nInstall Preact npm install preact \u0026\u0026 npm remove react-dom react Register paths in tsconfig: { \"compilerOptions\": { // ... \"paths\": { \"react\": [\"./node_modules/preact/compat/\"], \"react/jsx-runtime\": [\"./node_modules/preact/jsx-runtime\"], \"react-dom\": [\"./node_modules/preact/compat/\"], \"react-dom/*\": [\"./node_modules/preact/compat/*\"] } Register aliases in in config.exs esbuild flags - js/app.jsx ... + js/app.jsx --alias:react=preact/compat --alias:react-dom=preact/compat ... Note: while Inertia.js doesn't provide a dedicated Preact client, the regular React client works well under Preact's compatibility mode.\n",
      "tags": [
        "Phoenix",
        "Inertia.js",
        "Preact"
      ],
      "title": "Preact with Inertia Phoenix",
      "url": "https://ostapbrehin.com/preact-with-inertia-phoenix/"
    },
    {
      "content_html": "\u003cp\u003eAfter running \u003ccode\u003eliveinst\u003c/code\u003e from the terminal, I got this kind of error:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003ewebui-desktop line 197 kill no such process\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eTurns out, there is currently a bug where the installer wouldn\u0026#39;t start unless you patch \u003ccode\u003e/usr/libexec/anaconda/webui-desktop\u003c/code\u003e by changing the line 172 from:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eDEFAULT_BROWSER_CMD\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003eget_default_browser\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eto\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eDEFAULT_BROWSER_CMD\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003efirefox\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://discussion.fedoraproject.org/t/fedora-43-install-to-disk-fails/170413/22\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSource\u003c/a\u003e.\u003c/p\u003e\n",
      "date_modified": "2025-12-17T03:09:59Z",
      "date_published": "2025-12-17T03:00:11Z",
      "id": "https://ostapbrehin.com/fixing-fedora-43-i3-installer-startup-issue/",
      "summary": "After running liveinst from the terminal, I got this kind of error:\nwebui-desktop line 197 kill no such process\nTurns out, there is currently a bug where the installer wouldn't start unless you patch /usr/libexec/anaconda/webui-desktop by changing the line 172 from:\nDEFAULT_BROWSER_CMD=$(get_default_browser) to\nDEFAULT_BROWSER_CMD=firefox Source.\n",
      "tags": [
        "Fedora",
        "Linux",
        "Desktop"
      ],
      "title": "How to fix Fedora 43 (i3) installer startup issue",
      "url": "https://ostapbrehin.com/fixing-fedora-43-i3-installer-startup-issue/"
    },
    {
      "content_html": "\u003cp\u003eThis week, I tried out Cursor. The code it originally wrote was not up to my standards, which led me to making a set of custom LLM rules.\u003c/p\u003e\n\u003cp\u003eAlso check out \u003ca href=\"https://github.com/spatie/laravel-stubs\" target=\"_blank\" rel=\"noopener noreferrer\"\u003espatie/laravel-stubs\u003c/a\u003e for stubs that favor types as opposed to bloated PHPDoc comments.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003e.cursor/rules/laravel.mdc\u003c/code\u003e file:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ealwaysApply: true\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Do not use \u003cspan class=\"sb\"\u003e`auth()`\u003c/span\u003e helper or \u003cspan class=\"sb\"\u003e`Auth`\u003c/span\u003e facade, instead always retrieve the user either from the Request (\u003cspan class=\"sb\"\u003e`$request-\u0026gt;user()`\u003c/span\u003e) or via \u003cspan class=\"sb\"\u003e`#[CurrentUser] User $user`\u003c/span\u003e attribute (\u003cspan class=\"sb\"\u003e`use Illuminate\\Container\\Attributes\\CurrentUser;`\u003c/span\u003e). Prop-drill it down the methods instead of calling \u003cspan class=\"sb\"\u003e`Auth`\u003c/span\u003e or \u003cspan class=\"sb\"\u003e`auth`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Do not add comments because code is already self-explanatory.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Do not assign ids manually like \u003cspan class=\"sb\"\u003e`Post::create([\u0026#39;user_id\u0026#39; =\u0026gt; Auth::id()`\u003c/span\u003e, instead call relationship method on the parental model like \u003cspan class=\"sb\"\u003e`$user-\u0026gt;posts()-\u0026gt;create()`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Do not query ids manually like \u003cspan class=\"sb\"\u003e`where(\u0026#39;user_id\u0026#39;, $user-\u0026gt;id)`\u003c/span\u003e, always prefer methods such as  \u003cspan class=\"sb\"\u003e`whereBelongsTo($user)`\u003c/span\u003e, or \u003cspan class=\"sb\"\u003e`whereAttachedTo($role)`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Always prefer to write less code and start with \u003cspan class=\"sb\"\u003e`artisan make:`\u003c/span\u003e commands. For instance, you can get a model, resource controller and a migration all at once via \u003cspan class=\"sb\"\u003e`make:model -mcr`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Always use Policy classes as opposed to controller checks or unnecessary \u003cspan class=\"sb\"\u003e`authorize`\u003c/span\u003e in form requests. The best way is to attach a policy on the route itself via \u003cspan class=\"sb\"\u003e`Route::post(\u0026#39;/post\u0026#39;, ...)-\u0026gt;can(\u0026#39;create\u0026#39;, Post::class);`\u003c/span\u003e or \u003cspan class=\"sb\"\u003e`-\u0026gt;can(\u0026#39;update\u0026#39;, \u0026#39;post\u0026#39;)`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Put logic in the right place from the start by avoiding \u003cspan class=\"sb\"\u003e`private`\u003c/span\u003e/\u003cspan class=\"sb\"\u003e`protected`\u003c/span\u003e methods in controllers. Prefer dedicated methods in FormRequests, fat domain models and action classes.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Always use singular entity names for controllers, ie FormController but not FormsController.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Always use \u003cspan class=\"sb\"\u003e`$request-\u0026gt;validated()`\u003c/span\u003e instead of \u003cspan class=\"sb\"\u003e`$request-\u0026gt;all()`\u003c/span\u003e to avoid unexpected data injections because there is usually a \u003cspan class=\"sb\"\u003e`protected $guarded = [];`\u003c/span\u003e but never overrwrite \u003cspan class=\"sb\"\u003e`validated`\u003c/span\u003e - it already returns all keys defined in \u003cspan class=\"sb\"\u003e`rules`\u003c/span\u003e of the \u003cspan class=\"sb\"\u003e`FormReqiest`\u003c/span\u003e class.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Never manually close modal windows that have Inertia \u003cspan class=\"sb\"\u003e`\u0026lt;Form`\u003c/span\u003e inside - the page will reload and re-render automatically. This assumes avoiding \u003cspan class=\"sb\"\u003e`onSuccess`\u003c/span\u003e for all modals that have \u003cspan class=\"sb\"\u003e`\u0026lt;Form`\u003c/span\u003e component. \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Do not list out all the fields in controller like \u003cspan class=\"sb\"\u003e`StoreFormRequest $request) Model::create([\u0026#39;field\u0026#39; =\u0026gt; $request-\u0026gt;input(\u0026#39;field\u0026#39;)]);`\u003c/span\u003e if we already had them listed in a custom FormRequest class, instead always pass all via \u003cspan class=\"sb\"\u003e`$request-\u0026gt;validated()`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Do not be overly clever by trying to overwrite or redefine built-in framework methods such as \u003cspan class=\"sb\"\u003e`validated`\u003c/span\u003e or \u003cspan class=\"sb\"\u003e`all`\u003c/span\u003e in \u003cspan class=\"sb\"\u003e`FormRequest`\u003c/span\u003e. In this specific case use \u003cspan class=\"sb\"\u003e`protected prepareForValidation(): void`\u003c/span\u003e and \u003cspan class=\"sb\"\u003e`protected passedValidation(): void`\u003c/span\u003e or introduce new methods instead but not overwrite Laravel ones.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e When manipulating strings, try always prefer Laravel\u0026#39;s \u003cspan class=\"sb\"\u003e`Str::`\u003c/span\u003e and \u003cspan class=\"sb\"\u003e`Str::of(\u0026#39;source\u0026#39;)-\u0026gt;-\u0026gt;-\u0026gt;`\u003c/span\u003e methods for nice fluent chained readable string manipulations. Popular methods include \u003cspan class=\"sb\"\u003e`chopEnd`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`chopStart`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`beforeLast`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`trim`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`replace`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`contains`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`snake`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`slug`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`match`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Prefer \u003cspan class=\"sb\"\u003e`to_route(`\u003c/span\u003e as opposed to \u003cspan class=\"sb\"\u003e`redirect()-\u0026gt;route(`\u003c/span\u003e - it returns same \u003cspan class=\"sb\"\u003e`RedirectResponse`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e Do not check \u003cspan class=\"sb\"\u003e`$this-\u0026gt;has`\u003c/span\u003e or \u003cspan class=\"sb\"\u003e`$request-\u0026gt;has`\u003c/span\u003e or \u003cspan class=\"sb\"\u003e`if($this-\u0026gt;input(\u0026#39;field\u0026#39;))`\u003c/span\u003e for required validation fields because they have the \u003cspan class=\"sb\"\u003e`\u0026#39;required\u0026#39;`\u003c/span\u003e Laravel rule on them as part of \u003cspan class=\"sb\"\u003e`public function rules(): array`\u003c/span\u003e in \u003cspan class=\"sb\"\u003e`FormRequest`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e PHP 8.5 introduced \u003cspan class=\"sb\"\u003e`new \\Uri\\Rfc3986\\Uri(\u0026#39;https://example.com\u0026#39;)`\u003c/span\u003e with methods such as \u003cspan class=\"sb\"\u003e`getHost`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`getSchema`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`getPort`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`getQuery`\u003c/span\u003e, \u003cspan class=\"sb\"\u003e`getPath`\u003c/span\u003e.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e PHP 8.5 introduced pipe opeartor which accepts any callable \u003cspan class=\"sb\"\u003e`$result = \u0026#34;Hello World\u0026#34; |\u0026gt; my_function(...) |\u0026gt; \u0026#39;strtoupper\u0026#39; |\u0026gt; str_shuffle(...) |\u0026gt; fn($x) =\u0026gt; trim($x) |\u0026gt; function(string $x): string {return strtolower($x);} |\u0026gt; new MyClass() |\u0026gt; [MyClass::class, \u0026#39;myStaticMethod\u0026#39;] |\u0026gt; new MyClass()-\u0026gt;myInstanceMethod(...);`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e-\u003c/span\u003e PHP 8.5 introduced \u003cspan class=\"sb\"\u003e`array_first`\u003c/span\u003e and \u003cspan class=\"sb\"\u003e`array_last`\u003c/span\u003e; \u003cspan class=\"sb\"\u003e`#[\\Override]`\u003c/span\u003e attribute can now be applied to properties. With \u003cspan class=\"sb\"\u003e`#[\\NoDiscard]`\u003c/span\u003e PHP will check whether the returned value is consumed and emit a warning if it is not.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ePeace ✌️\u003c/p\u003e\n",
      "date_modified": "2025-12-26T11:14:05Z",
      "date_published": "2025-12-01T17:55:27Z",
      "id": "https://ostapbrehin.com/laravel-ai-rules/",
      "summary": "This week, I tried out Cursor. The code it originally wrote was not up to my standards, which led me to making a set of custom LLM rules.\nAlso check out spatie/laravel-stubs for stubs that favor types as opposed to bloated PHPDoc comments.\nThe .cursor/rules/laravel.mdc file:\n--- alwaysApply: true --- - Do not use `auth()` helper or `Auth` facade, instead always retrieve the user either from the Request (`$request-\u003euser()`) or via `#[CurrentUser] User $user` attribute (`use Illuminate\\Container\\Attributes\\CurrentUser;`). Prop-drill it down the methods instead of calling `Auth` or `auth`. - Do not add comments because code is already self-explanatory. - Do not assign ids manually like `Post::create(['user_id' =\u003e Auth::id()`, instead call relationship method on the parental model like `$user-\u003eposts()-\u003ecreate()`. - Do not query ids manually like `where('user_id', $user-\u003eid)`, always prefer methods such as `whereBelongsTo($user)`, or `whereAttachedTo($role)`. - Always prefer to write less code and start with `artisan make:` commands. For instance, you can get a model, resource controller and a migration all at once via `make:model -mcr`. - Always use Policy classes as opposed to controller checks or unnecessary `authorize` in form requests. The best way is to attach a policy on the route itself via `Route::post('/post', ...)-\u003ecan('create', Post::class);` or `-\u003ecan('update', 'post')`. - Put logic in the right place from the start by avoiding `private`/`protected` methods in controllers. Prefer dedicated methods in FormRequests, fat domain models and action classes. - Always use singular entity names for controllers, ie FormController but not FormsController. - Always use `$request-\u003evalidated()` instead of `$request-\u003eall()` to avoid unexpected data injections because there is usually a `protected $guarded = [];` but never overrwrite `validated` - it already returns all keys defined in `rules` of the `FormReqiest` class. - Never manually close modal windows that have Inertia `\u003cForm` inside - the page will reload and re-render automatically. This assumes avoiding `onSuccess` for all modals that have `\u003cForm` component. - Do not list out all the fields in controller like `StoreFormRequest $request) Model::create(['field' =\u003e $request-\u003einput('field')]);` if we already had them listed in a custom FormRequest class, instead always pass all via `$request-\u003evalidated()`. - Do not be overly clever by trying to overwrite or redefine built-in framework methods such as `validated` or `all` in `FormRequest`. In this specific case use `protected prepareForValidation(): void` and `protected passedValidation(): void` or introduce new methods instead but not overwrite Laravel ones. - When manipulating strings, try always prefer Laravel's `Str::` and `Str::of('source')-\u003e-\u003e-\u003e` methods for nice fluent chained readable string manipulations. Popular methods include `chopEnd`, `chopStart`, `beforeLast`, `trim`, `replace`, `contains`, `snake`, `slug`, `match`. - Prefer `to_route(` as opposed to `redirect()-\u003eroute(` - it returns same `RedirectResponse`. - Do not check `$this-\u003ehas` or `$request-\u003ehas` or `if($this-\u003einput('field'))` for required validation fields because they have the `'required'` Laravel rule on them as part of `public function rules(): array` in `FormRequest`. - PHP 8.5 introduced `new \\Uri\\Rfc3986\\Uri('https://example.com')` with methods such as `getHost`, `getSchema`, `getPort`, `getQuery`, `getPath`. - PHP 8.5 introduced pipe opeartor which accepts any callable `$result = \"Hello World\" |\u003e my_function(...) |\u003e 'strtoupper' |\u003e str_shuffle(...) |\u003e fn($x) =\u003e trim($x) |\u003e function(string $x): string {return strtolower($x);} |\u003e new MyClass() |\u003e [MyClass::class, 'myStaticMethod'] |\u003e new MyClass()-\u003emyInstanceMethod(...);` - PHP 8.5 introduced `array_first` and `array_last`; `#[\\Override]` attribute can now be applied to properties. With `#[\\NoDiscard]` PHP will check whether the returned value is consumed and emit a warning if it is not. Peace ✌️\n",
      "tags": [
        "AI",
        "Laravel",
        "Cursor",
        "Standards"
      ],
      "title": "Sane set of Cursor rules for Laravel development",
      "url": "https://ostapbrehin.com/laravel-ai-rules/"
    },
    {
      "content_html": "\u003ch2 id=\"why-dokku-rather-than-forge-or-ploi\"\u003e\u003ca class=\"heading-anchor\" href=\"#why-dokku-rather-than-forge-or-ploi\"\u003eWhy Dokku rather than Forge or Ploi?\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eMy reasons:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e🔋 Zero downtime deploys baked-in\u003c/li\u003e\n\u003cli\u003e🔒 Each app is isolated by Docker\u003c/li\u003e\n\u003cli\u003e💸 No additional costs or security risks\u003c/li\u003e\n\u003cli\u003e♾️ Not limited to PHP (anything deploys)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIn comparison, Forge supports zero downtime deploys by making you subscribe to \u003ca href=\"https://envoyer.io\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eEnvoyer\u003c/a\u003e (10$/mo) or reaching out for \u003ca href=\"https://chrysanthos.xyz/article/zero-downtime-deployments-for-laravel\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eother hacks\u003c/a\u003e. Even then, such setup would be limited to PHP-FPM, making it impossible to run Octane or other languages/runtimes.\u003c/p\u003e\n\u003ch2 id=\"setting-an-encryption-key\"\u003e\u003ca class=\"heading-anchor\" href=\"#setting-an-encryption-key\"\u003eSetting an encryption key\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eRun this local command to receive a new encryption key:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ephp artisan key:generate --show\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen copy the key and set it remotely:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edokku config:set appname \u003cspan class=\"nv\"\u003eAPP_KEY\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;YOUR_KEY\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"sqlite-as-your-database\"\u003e\u003ca class=\"heading-anchor\" href=\"#sqlite-as-your-database\"\u003eSQLite as your database\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eDokku stores persistent data in a \u0026ldquo;persistent\u0026rdquo; directory, so let\u0026#39;s create one:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edokku storage:ensure-directory appname-db\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen we can enter it to make a new production-ready WAL-mode database:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e /var/lib/dokku/data/storage/appname-db\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esqlite3 database.sqlite \u003cspan class=\"s2\"\u003e\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e  PRAGMA journal_mode = WAL;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e  PRAGMA auto_vacuum = INCREMENTAL;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e  PRAGMA page_size = 4096;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e  PRAGMA wal_autocheckpoint = 1000;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e  VACUUM;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFinally, let\u0026#39;s mount our directory to the app itself:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edokku storage:mount appname /var/lib/dokku/data/storage/appname-db:/app/database/sqlite\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAnd set the required environment variable:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edokku config:set appname \u003cspan class=\"nv\"\u003eDB_DATABASE\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e/app/database/sqlite/database.sqlite\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"octane-for-max-performance-dockerfile\"\u003e\u003ca class=\"heading-anchor\" href=\"#octane-for-max-performance-dockerfile\"\u003eOctane for max performance (Dockerfile)\u003c/a\u003e\u003c/h2\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-Dockerfile\" data-lang=\"Dockerfile\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e### Stage 1: Asset Build\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eFROM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003enode:20\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eAS\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003eassets-builder\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eWORKDIR\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e/app\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e package.json pnpm-lock.yaml ./\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e corepack enable\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e --mount\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003ecache,id\u003cspan class=\"o\"\u003e=\u003c/span\u003epnpm,target\u003cspan class=\"o\"\u003e=\u003c/span\u003e/root/.local/share/pnpm/store pnpm install --frozen-lockfile\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e . .\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e pnpm build\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e### Stage 2: Laravel Build\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eFROM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003ecomposer:2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eAS\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003edependencies\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eWORKDIR\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e/app\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e . .\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e composer install --no-dev --optimize-autoloader --no-interaction\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e### Stage 3: Final Image\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eFROM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003edunglas/frankenphp\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e --from\u003cspan class=\"o\"\u003e=\u003c/span\u003ecomposer /usr/bin/composer /usr/bin/composer\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eRUN\u003c/span\u003e install-php-extensions pcntl zip\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eWORKDIR\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e/app\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e . .\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e --from\u003cspan class=\"o\"\u003e=\u003c/span\u003edependencies /app/vendor /app/vendor\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e --from\u003cspan class=\"o\"\u003e=\u003c/span\u003eassets-builder /app/public/build /app/public/build\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eEXPOSE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e80\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCMD\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;php\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;artisan\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;octane:start\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;--server=frankenphp\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;--host=0.0.0.0\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;--port=80\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;--admin-port=2019\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"queues\"\u003e\u003ca class=\"heading-anchor\" href=\"#queues\"\u003eQueues\u003c/a\u003e\u003c/h2\u003e\u003cp\u003e\u003ccode\u003eProcfile\u003c/code\u003e lets you define multiple processes for your app. Here\u0026#39;s mine:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eweb: php artisan octane:start --server\u003cspan class=\"o\"\u003e=\u003c/span\u003efrankenphp --host\u003cspan class=\"o\"\u003e=\u003c/span\u003e0.0.0.0 --port\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e80\u003c/span\u003e --admin-port\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e2019\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003erelease: php artisan migrate --force\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003equeue: php artisan queue:work\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBut, any new process won\u0026#39;t be activated until we scale it up:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edokku ps:scale appname \u003cspan class=\"nv\"\u003equeue\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"server-and-provisioning\"\u003e\u003ca class=\"heading-anchor\" href=\"#server-and-provisioning\"\u003eServer and provisioning\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eI prefer to use Debian 12 as it does not come with any extra bloat such as Snap on Ubuntu and thus uses minimum resources. It\u0026#39;s been proven to be super stable and run for years without reboots.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eapt install unattended-upgrades -y\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eDisable password login for SSH:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003esed -i -E \u0026#39;s/#?PasswordAuthentication (yes|no)/PasswordAuthentication no/\u0026#39; /etc/ssh/sshd_config\nsystemctl reload ssh\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e(assuming you have your SSH keys in \u003ccode\u003eauthorized_keys\u003c/code\u003e)\u003c/p\u003e\n\u003ch2 id=\"optimizations\"\u003e\u003ca class=\"heading-anchor\" href=\"#optimizations\"\u003eOptimizations\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eYou will get a much better resource utilization by enabling keepalive:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edokku nginx:set --global proxy-keepalive \u003cspan class=\"m\"\u003e64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWithout it, there will consistently be a new TCP connection opened between Nginx and the Docker container, eventually making it hit the machine\u0026#39;s Linux configuration limit. Setting a keepalive to your Nginx proxy configuration prevents it.\u003c/p\u003e\n\u003ch2 id=\"troubleshooting\"\u003e\u003ca class=\"heading-anchor\" href=\"#troubleshooting\"\u003eTroubleshooting\u003c/a\u003e\u003c/h2\u003e\u003cp\u003eReset port configuration to a default state:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003edokku ports:clear appname\ndokku ports:set appname http:80:80\ndokku ports:set appname https:443:80\ndokku ps:restart appname\n\u003c/code\u003e\u003c/pre\u003e",
      "date_modified": "2026-10-01T04:57:20+01:00",
      "date_published": "2025-01-12T23:28:29Z",
      "id": "https://ostapbrehin.com/dokku-with-laravel-cookbook/",
      "summary": "Why Dokku rather than Forge or Ploi?My reasons:\n🔋 Zero downtime deploys baked-in 🔒 Each app is isolated by Docker 💸 No additional costs or security risks ♾️ Not limited to PHP (anything deploys) In comparison, Forge supports zero downtime deploys by making you subscribe to Envoyer (10$/mo) or reaching out for other hacks. Even then, such setup would be limited to PHP-FPM, making it impossible to run Octane or other languages/runtimes.\n",
      "tags": [
        "Servers",
        "Deployment",
        "Laravel",
        "DevOps"
      ],
      "title": "Dokku with Laravel cookbook",
      "url": "https://ostapbrehin.com/dokku-with-laravel-cookbook/"
    },
    {
      "content_html": "\u003cp\u003eПол Грем, відомий підприємець і засновник Y Combinator, любить повторювати принцип, якому навчився від Пола Бухейта:\nкраще зробити кількох людей дуже щасливими, ніж багатьох - трохи щасливими. Ось 13 ключових порад для стартаперів від Пола Грема:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eВибирайте класних співзасновників.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eСпівзасновники для стартапу - як локація для нерухомості. Ви можете змінити все, крім цього. Успіх стартапу майже завжди\nзалежить від його засновників.\u003c/p\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003e\u003cstrong\u003eЗапускайтесь швидко.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eСправа не в тому, щоб першим вийти на ринок, а в тому, що ви не почнете по-справжньому працювати, поки не запуститесь.\nЗапуск навчить вас, що насправді треба було будувати.\u003c/p\u003e\n\u003col start=\"3\"\u003e\n\u003cli\u003e\u003cstrong\u003eДозвольте ідеї розвиватися.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eНе думайте, що стартап - це просто втілення блискучої початкової ідеї. Як і в есе, більшість ідей з\u0026#39;являються в процесі\nреалізації.\u003c/p\u003e\n\u003col start=\"4\"\u003e\n\u003cli\u003e\u003cstrong\u003eЗрозумійте своїх користувачів.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eУявіть успіх стартапу як прямокутник: одна сторона - кількість користувачів, інша - наскільки ви покращуєте їхнє життя.\nДруга сторона під вашим контролем, і вона впливає на першу.\u003c/p\u003e\n\u003col start=\"5\"\u003e\n\u003cli\u003e\u003cstrong\u003eКраще мати кількох фанатів, ніж багато байдужих користувачів.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eСпочатку зосередьтесь на тому, щоб повністю задовольнити потреби невеликої групи користувачів. Легше розширювати\nаудиторію, аніж рівень задоволеності.\u003c/p\u003e\n\u003col start=\"6\"\u003e\n\u003cli\u003e\u003cstrong\u003eВражайте клієнтським сервісом.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eЛюди звикли до поганого обслуговування. Здивуйте їх своєю турботою. На ранніх етапах це також допоможе вам краще\nзрозуміти користувачів.\u003c/p\u003e\n\u003col start=\"7\"\u003e\n\u003cli\u003e\u003cstrong\u003eЩо міряєш, те й росте.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eПросте вимірювання чогось магічним чином покращує це. Хочете більше користувачів? Почніть щодня відмічати їх кількість\nна великому аркуші.\u003c/p\u003e\n\u003col start=\"8\"\u003e\n\u003cli\u003e\u003cstrong\u003eЕкономте гроші.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eБагато стартапів помирають, бо в них закінчуються гроші. Економність дозволяє вам швидше експериментувати і довше\nтриматися на плаву.\u003c/p\u003e\n\u003col start=\"9\"\u003e\n\u003cli\u003e\u003cstrong\u003eДосягніть \u0026ldquo;рамен-прибутковості\u0026rdquo;.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eЦе коли ви заробляєте достатньо, щоб покрити базові витрати засновників. Це змінює ваші стосунки з інвесторами і\nпіднімає бойовий дух.\u003c/p\u003e\n\u003col start=\"10\"\u003e\n\u003cli\u003e\u003cstrong\u003eУникайте відволікань.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eОсобливо тих, які приносять гроші зараз - підробітків, консультацій. Навіть збір коштів може відволікати. Зосередьтесь\nна головному.\u003c/p\u003e\n\u003col start=\"11\"\u003e\n\u003cli\u003e\u003cstrong\u003eНе падайте духом.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eХоч стартапи часто \u0026ldquo;вмирають\u0026rdquo; через брак грошей, справжня причина - втрата фокусу через деморалізацію. Усвідомте цей\nтягар і свідомо боріться з ним.\u003c/p\u003e\n\u003col start=\"12\"\u003e\n\u003cli\u003e\u003cstrong\u003eНе здавайтесь.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eУ стартапах наполегливість часто важливіша за талант. Ви можете досягти дивовижних результатів, просто не здаючись і\nпостійно адаптуючи свою ідею.\u003c/p\u003e\n\u003col start=\"13\"\u003e\n\u003cli\u003e\u003cstrong\u003eБудьте готові, що угоди зриваються.\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eНе покладайте всі надії на одну велику угоду. Ставтесь до них як до фонових процесів - ігноруйте, поки вони не\nзавершаться.\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eІ найголовніше - \u003cstrong\u003eрозумійте своїх користувачів\u003c/strong\u003e. Це ключ до всього. Ваше завдання - створювати цінність, покращуючи\nжиття користувачів. Найскладніше - зрозуміти, що саме їм потрібно. Коли ви це знаєте, все інше - лише питання зусиль.\u003c/p\u003e\n\u003cp\u003eПам\u0026#39;ятайте: навіть коли все навколо валиться, наявність лише 10 користувачів, які обожнюють ваш продукт, може стати тим,\nщо допоможе вам рухатися вперед.\u003c/p\u003e\n",
      "date_modified": "2025-01-13T00:18:36Z",
      "date_published": "2024-08-18T23:35:54Z",
      "id": "https://ostapbrehin.com/startapy-v-13-rechennyakh/",
      "summary": "Пол Грем, відомий підприємець і засновник Y Combinator, любить повторювати принцип, якому навчився від Пола Бухейта: краще зробити кількох людей дуже щасливими, ніж багатьох - трохи щасливими. Ось 13 ключових порад для стартаперів від Пола Грема:\nВибирайте класних співзасновників. Співзасновники для стартапу - як локація для нерухомості. Ви можете змінити все, крім цього. Успіх стартапу майже завжди залежить від його засновників.\nЗапускайтесь швидко. Справа не в тому, щоб першим вийти на ринок, а в тому, що ви не почнете по-справжньому працювати, поки не запуститесь. Запуск навчить вас, що насправді треба було будувати.\n",
      "title": "Стартапи в 13 реченнях від Пола Грема",
      "url": "https://ostapbrehin.com/startapy-v-13-rechennyakh/"
    }
  ],
  "language": "en-US",
  "title": "Ostap Brehin",
  "version": "https://jsonfeed.org/version/1.1"
}