[{"data":1,"prerenderedAt":834},["ShallowReactive",2],{"navigation":3,"/docs/guide/understanding-file-permissions":221,"/docs/guide/understanding-file-permissions-surround":829},[4],{"title":5,"path":6,"stem":7,"children":8},"Docs","/docs","docs",[9,12,56,85,132,153,178,195,208],{"title":10,"path":6,"stem":11},"","docs/index",{"title":13,"path":14,"stem":15,"children":16,"icon":55},"Getting Started","/docs/getting-started","docs/1.getting-started/1.index",[17,19,23,27,31,35,39,43,47,51],{"title":18,"path":14,"stem":15},"Introduction",{"title":20,"path":21,"stem":22},"Container Basics","/docs/getting-started/container-basics","docs/1.getting-started/2.container-basics",{"title":24,"path":25,"stem":26},"Installation","/docs/getting-started/installation","docs/1.getting-started/3.installation",{"title":28,"path":29,"stem":30},"These Images vs Others","/docs/getting-started/these-images-vs-others","docs/1.getting-started/4.these-images-vs-others",{"title":32,"path":33,"stem":34},"Choosing an Image","/docs/getting-started/choosing-an-image","docs/1.getting-started/5.choosing-an-image",{"title":36,"path":37,"stem":38},"Default Configurations","/docs/getting-started/default-configurations","docs/1.getting-started/6.default-configurations",{"title":40,"path":41,"stem":42},"Upgrade Guide","/docs/getting-started/upgrade-guide","docs/1.getting-started/7.upgrade-guide",{"title":44,"path":45,"stem":46},"Changelog","/docs/getting-started/changelog","docs/1.getting-started/8.changelog",{"title":48,"path":49,"stem":50},"About","/docs/getting-started/about","docs/1.getting-started/9.about",{"title":52,"path":53,"stem":54},"Contributing","/docs/getting-started/contributing","docs/1.getting-started/99.contributing",false,{"title":57,"path":58,"stem":59,"children":60,"page":55},"Image Variations","/docs/image-variations","docs/2.image-variations",[61,65,69,73,77,81],{"title":62,"path":63,"stem":64},"CLI","/docs/image-variations/cli","docs/2.image-variations/cli",{"title":66,"path":67,"stem":68},"FPM","/docs/image-variations/fpm","docs/2.image-variations/fpm",{"title":70,"path":71,"stem":72},"FPM-Apache","/docs/image-variations/fpm-apache","docs/2.image-variations/fpm-apache",{"title":74,"path":75,"stem":76},"FPM-NGINX","/docs/image-variations/fpm-nginx","docs/2.image-variations/fpm-nginx",{"title":78,"path":79,"stem":80},"FrankenPHP","/docs/image-variations/frankenphp","docs/2.image-variations/frankenphp",{"title":82,"path":83,"stem":84},"Unit (Deprecated)","/docs/image-variations/unit","docs/2.image-variations/unit",{"title":86,"path":87,"stem":88,"children":89,"page":55},"Framework Guides","/docs/framework-guides","docs/3.framework-guides",[90,123],{"title":91,"icon":55,"defaultOpen":55,"path":92,"stem":93,"children":94,"page":55},"Laravel","/docs/framework-guides/laravel","docs/3.framework-guides/1.laravel",[95,99,103,107,111,115,119],{"title":96,"path":97,"stem":98},"Automations","/docs/framework-guides/laravel/automations","docs/3.framework-guides/1.laravel/1.automations",{"title":100,"path":101,"stem":102},"Task Scheduler","/docs/framework-guides/laravel/task-scheduler","docs/3.framework-guides/1.laravel/2.task-scheduler",{"title":104,"path":105,"stem":106},"Queue","/docs/framework-guides/laravel/queue","docs/3.framework-guides/1.laravel/3.queue",{"title":108,"path":109,"stem":110},"Horizon","/docs/framework-guides/laravel/horizon","docs/3.framework-guides/1.laravel/4.horizon",{"title":112,"path":113,"stem":114},"Reverb","/docs/framework-guides/laravel/reverb","docs/3.framework-guides/1.laravel/4.reverb",{"title":116,"path":117,"stem":118},"Nightwatch","/docs/framework-guides/laravel/nightwatch","docs/3.framework-guides/1.laravel/5.nightwatch",{"title":120,"path":121,"stem":122},"Octane","/docs/framework-guides/laravel/octane","docs/3.framework-guides/1.laravel/octane",{"title":124,"icon":55,"defaultOpen":55,"path":125,"stem":126,"children":127,"page":55},"WordPress","/docs/framework-guides/wordpress","docs/3.framework-guides/2.wordpress",[128],{"title":129,"path":130,"stem":131},"Using Docker with WordPress","/docs/framework-guides/wordpress/using-wordpress-with-docker","docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker",{"title":133,"path":134,"stem":135,"children":136,"page":55},"Deployment And Production","/docs/deployment-and-production","docs/4.deployment-and-production",[137,141,145,149],{"title":138,"path":139,"stem":140},"Development to Production","/docs/deployment-and-production/development-to-production","docs/4.deployment-and-production/2.development-to-production",{"title":142,"path":143,"stem":144},"Packaging Your App for Deployment","/docs/deployment-and-production/packaging-your-app-for-deployment","docs/4.deployment-and-production/3.packaging-your-app-for-deployment",{"title":146,"path":147,"stem":148},"Configuring SSL","/docs/deployment-and-production/configuring-ssl","docs/4.deployment-and-production/4.configuring-ssl",{"title":150,"path":151,"stem":152},"Choosing a Host","/docs/deployment-and-production/choosing-a-host","docs/4.deployment-and-production/5.choosing-a-host",{"title":154,"icon":55,"defaultOpen":55,"path":155,"stem":156,"children":157,"page":55},"Advanced Guides","/docs/guide","docs/5.guide",[158,162,166,170,174],{"title":159,"path":160,"stem":161},"Migrating from official PHP images","/docs/guide/migrating-from-official-php-images","docs/5.guide/1.migrating-from-official-php-images",{"title":163,"path":164,"stem":165},"Using Healthchecks With Laravel","/docs/guide/using-healthchecks-with-laravel","docs/5.guide/2.using-healthchecks-with-laravel",{"title":167,"path":168,"stem":169},"Using S6 Overlay","/docs/guide/using-s6-overlay","docs/5.guide/2.using-s6-overlay",{"title":171,"path":172,"stem":173},"Understanding File Permissions","/docs/guide/understanding-file-permissions","docs/5.guide/3.understanding-file-permissions",{"title":175,"path":176,"stem":177},"Configuring Trusted Proxies","/docs/guide/configuring-trusted-proxies","docs/5.guide/4.configuring-trusted-proxies",{"title":179,"icon":55,"defaultOpen":55,"path":180,"stem":181,"children":182,"page":55},"Customization","/docs/customizing-the-image","docs/6.customizing-the-image",[183,187,191],{"title":184,"path":185,"stem":186},"Changing php.ini settings","/docs/customizing-the-image/changing-common-php-settings","docs/6.customizing-the-image/1.changing-common-php-settings",{"title":188,"path":189,"stem":190},"Installing PHP extensions","/docs/customizing-the-image/installing-additional-php-extensions","docs/6.customizing-the-image/2.installing-additional-php-extensions",{"title":192,"path":193,"stem":194},"Adding Start Up Scripts","/docs/customizing-the-image/adding-your-own-start-up-scripts","docs/6.customizing-the-image/3.adding-your-own-start-up-scripts",{"title":196,"path":197,"stem":198,"children":199,"page":55},"Troubleshooting","/docs/troubleshooting","docs/7.troubleshooting",[200,204],{"title":201,"path":202,"stem":203},"Common Issues","/docs/troubleshooting/common-issues","docs/7.troubleshooting/1.common-issues",{"title":205,"path":206,"stem":207},"Getting Help","/docs/troubleshooting/getting-help","docs/7.troubleshooting/2.getting-help",{"title":209,"path":210,"stem":211,"children":212,"page":55},"Reference","/docs/reference","docs/8.reference",[213,217],{"title":214,"path":215,"stem":216},"Environment Variable Specification","/docs/reference/environment-variable-specification","docs/8.reference/1.environment-variable-specification",{"title":218,"path":219,"stem":220},"Command Reference","/docs/reference/command-reference","docs/8.reference/2.command-reference",{"id":222,"title":171,"body":223,"description":822,"extension":807,"links":823,"meta":824,"navigation":411,"path":172,"redirect":823,"seo":827,"stem":173,"__hash__":828},"docs/docs/5.guide/3.understanding-file-permissions.md",{"type":224,"value":225,"toc":815},"minimark",[226,233,241,246,258,265,269,284,288,291,301,304,309,347,351,360,629,632,747,771,774,787,791,794,811],[227,228,229],"lead-p",{},[230,231,232],"p",{},"Working with file permissions is one of the biggest headaches when working with PHP + Docker. This generally is because the PHP server also requires a web server to serve static files. By default, this means multiple users are created in the container, and permissions can get out of hand quickly.",[230,234,235],{},[236,237],"img",{":zoom":238,"alt":239,"src":240},"false","Traditional PHP File Permissions Configuration","images/docs/permissions-privileged.png",[242,243,245],"h2",{"id":244},"even-more-frustrating-development-environments","Even more frustrating: Development Environments",[230,247,248,249,253,254,257],{},"Even if someone configured a single user in the container to run both the PHP server and the web server, things get even more complicated in development environments. For example, if you have Alice running her Windows Machine with WSL2, she might have a user ID of ",[250,251,252],"code",{},"1001",". Then you have Bob running his Ubuntu workstation with a user ID of ",[250,255,256],{},"1002",". Meanwhile, Charlie is running his Docker on his macOS machine (that runs a tiny VM) that has a totally different file permission experience compared to Windows and Linux because of the file system differences.",[230,259,260,261,264],{},"If a volume is mounted from the container to the host, the container will write files to the host as ",[250,262,263],{},"33:33",", which will require sudo/root permissions to edit and delete files.",[242,266,268],{"id":267},"our-industry-attempted-workarounds","Our industry attempted workarounds",[230,270,271,272,275,276,279,280,283],{},"We've seen experiences that allow users to provide an environment variable of ",[250,273,274],{},"PUID"," and ",[250,277,278],{},"PGID",". Although this is a great user experience, it requires the container user to be privileged, which is a major \"no-no\" in the security world. It also had downstream file permission errors if the container failed on initialization where logs would be created by the root user and no longer writable by the ",[250,281,282],{},"www-data"," user.",[242,285,287],{"id":286},"our-solution","Our solution",[230,289,290],{},"We focus on providing the tools to give sysadmins the ability to:",[292,293,294,298],"ul",{},[295,296,297],"li",{},"Keep their containers unprivileged by default",[295,299,300],{},"Allow the dynamic reconfiguration of the container user and group ID (at build time only)",[230,302,303],{},"It's a bummer that we can only set the user and group ID at build time, but it's a small price to pay for the security benefits of running unprivileged containers.",[305,306,308],"h4",{"id":307},"how-it-works","How it works",[292,310,311,324,334,341],{},[295,312,313,314,316,317,319,320,323],{},"By default, all our images run ",[250,315,282],{}," as the user (",[250,318,263],{}," for Debian and ",[250,321,322],{},"82:82"," for Alpine)",[295,325,326,327,329,330,333],{},"We provide a script that can be called at build time to change the UID and GID of ",[250,328,282],{}," (called ",[250,331,332],{},"docker-php-serversideup-set-id",")",[295,335,336,337,340],{},"If you need to update permissions of service files (example: NGINX, Apache, FrankenPHP, etc), you can run the ",[250,338,339],{},"docker-php-serversideup-set-file-permissions"," at build. This will automatically detect the service and update the file permissions accordingly.",[295,342,343,344,346],{},"We will use a multi-stage build to ensure that the ",[250,345,332],{}," script is not executed in the construction of the final image",[242,348,350],{"id":349},"example","Example",[230,352,353,354,356,357,359],{},"Here's an example of ensuring our UID/GID of ",[250,355,282],{}," will match the development UID/GID of the host machine, while preserving the default UID/GID of ",[250,358,263],{}," for the final image:",[361,362,367],"pre",{"className":363,"code":364,"filename":365,"language":366,"meta":10,"style":10},"language-dockerfile shiki shiki-themes github-dark","############################################\n# Base Image\n############################################\nFROM serversideup/php:8.5-fpm-nginx-bookworm AS base\n\n############################################\n# Development Image\n############################################\nFROM base AS development\n\n# Switch to root so we can do root things\nUSER root\n\n# Save the build arguments as a variable\nARG USER_ID\nARG GROUP_ID\n\n# Use the build arguments to change the UID \n# and GID of www-data while also changing \n# the file permissions for NGINX\nRUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \\\n    \\\n    # Update the file permissions to match the new UID/GID\n    docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID\n\n# Drop back to our unprivileged user\nUSER www-data\n\n############################################\n# Production Image\n############################################\n\n# Since we're calling \"base\", production isn't\n# calling any of that permission stuff\nFROM base AS production\n\n# Copy our app files as www-data (33:33)\nCOPY --chown=www-data:www-data . /var/www/html\n","Dockerfile","dockerfile",[250,368,369,378,384,389,406,413,418,424,429,442,447,453,462,467,473,482,490,495,501,507,513,522,528,534,540,545,551,559,564,569,575,580,585,591,597,609,614,620],{"__ignoreMap":10},[370,371,374],"span",{"class":372,"line":373},"line",1,[370,375,377],{"class":376},"sAwPA","############################################\n",[370,379,381],{"class":372,"line":380},2,[370,382,383],{"class":376},"# Base Image\n",[370,385,387],{"class":372,"line":386},3,[370,388,377],{"class":376},[370,390,392,396,400,403],{"class":372,"line":391},4,[370,393,395],{"class":394},"snl16","FROM",[370,397,399],{"class":398},"s95oV"," serversideup/php:8.5-fpm-nginx-bookworm ",[370,401,402],{"class":394},"AS",[370,404,405],{"class":398}," base\n",[370,407,409],{"class":372,"line":408},5,[370,410,412],{"emptyLinePlaceholder":411},true,"\n",[370,414,416],{"class":372,"line":415},6,[370,417,377],{"class":376},[370,419,421],{"class":372,"line":420},7,[370,422,423],{"class":376},"# Development Image\n",[370,425,427],{"class":372,"line":426},8,[370,428,377],{"class":376},[370,430,432,434,437,439],{"class":372,"line":431},9,[370,433,395],{"class":394},[370,435,436],{"class":398}," base ",[370,438,402],{"class":394},[370,440,441],{"class":398}," development\n",[370,443,445],{"class":372,"line":444},10,[370,446,412],{"emptyLinePlaceholder":411},[370,448,450],{"class":372,"line":449},11,[370,451,452],{"class":376},"# Switch to root so we can do root things\n",[370,454,456,459],{"class":372,"line":455},12,[370,457,458],{"class":394},"USER",[370,460,461],{"class":398}," root\n",[370,463,465],{"class":372,"line":464},13,[370,466,412],{"emptyLinePlaceholder":411},[370,468,470],{"class":372,"line":469},14,[370,471,472],{"class":376},"# Save the build arguments as a variable\n",[370,474,476,479],{"class":372,"line":475},15,[370,477,478],{"class":394},"ARG",[370,480,481],{"class":398}," USER_ID\n",[370,483,485,487],{"class":372,"line":484},16,[370,486,478],{"class":394},[370,488,489],{"class":398}," GROUP_ID\n",[370,491,493],{"class":372,"line":492},17,[370,494,412],{"emptyLinePlaceholder":411},[370,496,498],{"class":372,"line":497},18,[370,499,500],{"class":376},"# Use the build arguments to change the UID \n",[370,502,504],{"class":372,"line":503},19,[370,505,506],{"class":376},"# and GID of www-data while also changing \n",[370,508,510],{"class":372,"line":509},20,[370,511,512],{"class":376},"# the file permissions for NGINX\n",[370,514,516,519],{"class":372,"line":515},21,[370,517,518],{"class":394},"RUN",[370,520,521],{"class":398}," docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \\\n",[370,523,525],{"class":372,"line":524},22,[370,526,527],{"class":398},"    \\\n",[370,529,531],{"class":372,"line":530},23,[370,532,533],{"class":376},"    # Update the file permissions to match the new UID/GID\n",[370,535,537],{"class":372,"line":536},24,[370,538,539],{"class":398},"    docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID\n",[370,541,543],{"class":372,"line":542},25,[370,544,412],{"emptyLinePlaceholder":411},[370,546,548],{"class":372,"line":547},26,[370,549,550],{"class":376},"# Drop back to our unprivileged user\n",[370,552,554,556],{"class":372,"line":553},27,[370,555,458],{"class":394},[370,557,558],{"class":398}," www-data\n",[370,560,562],{"class":372,"line":561},28,[370,563,412],{"emptyLinePlaceholder":411},[370,565,567],{"class":372,"line":566},29,[370,568,377],{"class":376},[370,570,572],{"class":372,"line":571},30,[370,573,574],{"class":376},"# Production Image\n",[370,576,578],{"class":372,"line":577},31,[370,579,377],{"class":376},[370,581,583],{"class":372,"line":582},32,[370,584,412],{"emptyLinePlaceholder":411},[370,586,588],{"class":372,"line":587},33,[370,589,590],{"class":376},"# Since we're calling \"base\", production isn't\n",[370,592,594],{"class":372,"line":593},34,[370,595,596],{"class":376},"# calling any of that permission stuff\n",[370,598,600,602,604,606],{"class":372,"line":599},35,[370,601,395],{"class":394},[370,603,436],{"class":398},[370,605,402],{"class":394},[370,607,608],{"class":398}," production\n",[370,610,612],{"class":372,"line":611},36,[370,613,412],{"emptyLinePlaceholder":411},[370,615,617],{"class":372,"line":616},37,[370,618,619],{"class":376},"# Copy our app files as www-data (33:33)\n",[370,621,623,626],{"class":372,"line":622},38,[370,624,625],{"class":394},"COPY",[370,627,628],{"class":398}," --chown=www-data:www-data . /var/www/html\n",[230,630,631],{},"To show a simple Docker Compose file example for development, we could use:",[361,633,638],{"className":634,"code":635,"filename":636,"language":637,"meta":10,"style":10},"language-yml shiki shiki-themes github-dark","services:\n  php:\n    build:\n      context: .\n      target: development\n      args:\n        # UID and GID must be set as environment variables on the host machine\n        USER_ID: $UID\n        GROUP_ID: $GID\n    ports:\n      - 80:8080\n    volumes:\n      - .:/var/www/html\n","compose.yml","yml",[250,639,640,649,656,663,675,686,693,698,708,718,725,733,740],{"__ignoreMap":10},[370,641,642,646],{"class":372,"line":373},[370,643,645],{"class":644},"s4JwU","services",[370,647,648],{"class":398},":\n",[370,650,651,654],{"class":372,"line":380},[370,652,653],{"class":644},"  php",[370,655,648],{"class":398},[370,657,658,661],{"class":372,"line":386},[370,659,660],{"class":644},"    build",[370,662,648],{"class":398},[370,664,665,668,671],{"class":372,"line":391},[370,666,667],{"class":644},"      context",[370,669,670],{"class":398},": ",[370,672,674],{"class":673},"sDLfK",".\n",[370,676,677,680,682],{"class":372,"line":408},[370,678,679],{"class":644},"      target",[370,681,670],{"class":398},[370,683,685],{"class":684},"sU2Wk","development\n",[370,687,688,691],{"class":372,"line":415},[370,689,690],{"class":644},"      args",[370,692,648],{"class":398},[370,694,695],{"class":372,"line":420},[370,696,697],{"class":376},"        # UID and GID must be set as environment variables on the host machine\n",[370,699,700,703,705],{"class":372,"line":426},[370,701,702],{"class":644},"        USER_ID",[370,704,670],{"class":398},[370,706,707],{"class":684},"$UID\n",[370,709,710,713,715],{"class":372,"line":431},[370,711,712],{"class":644},"        GROUP_ID",[370,714,670],{"class":398},[370,716,717],{"class":684},"$GID\n",[370,719,720,723],{"class":372,"line":444},[370,721,722],{"class":644},"    ports",[370,724,648],{"class":398},[370,726,727,730],{"class":372,"line":449},[370,728,729],{"class":398},"      - ",[370,731,732],{"class":684},"80:8080\n",[370,734,735,738],{"class":372,"line":455},[370,736,737],{"class":644},"    volumes",[370,739,648],{"class":398},[370,741,742,744],{"class":372,"line":464},[370,743,729],{"class":398},[370,745,746],{"class":684},".:/var/www/html\n",[230,748,749,750,753,754,757,758,760,761,763,764,275,767,770],{},"When we run ",[250,751,752],{},"docker compose up",", our compose file directs us to build the ",[250,755,756],{},"development"," target. This target will run the ",[250,759,332],{}," script to change the UID and GID of ",[250,762,282],{}," to match the host machine (assuming ",[250,765,766],{},"$UID",[250,768,769],{},"$GID"," are set in a zsh/bash profile or something similar). This will allow us to run the container as an unprivileged user while still having the correct permissions to read and write files.",[230,772,773],{},"The best thing is the user can delete files off of their machine without being prompted for sudo permissions. This is because we're aligning the UID/GID of the container with the host machine.",[230,775,776,777,780,781,783,784,786],{},"When it comes to building our image for production, we just use the ",[250,778,779],{},"production"," target, which will copy the files as ",[250,782,282],{}," with the default UID/GID of ",[250,785,263],{},".",[242,788,790],{"id":789},"an-optimized-experience-from-development-to-production","An optimized experience from development to production",[230,792,793],{},"If you like the concepts above and you're looking for an optimized experience for developers (especially when it comes simplifying the setting of UID/GID), we recommend checking out our other open source project Spin. Spin is a lightweight wrapper for Docker Compose that allows you to manage your environment from development to production.",[795,796],"u-button",{"ariaLabel":797,"className":798,"color":806,"label":797,"size":807,"to":808,"trailing-icon":809,"variant":810},"Learn more about Spin",[799,800,801,802,803,804,805],"font-bold","ring","ring-inset","ring-blue-600","text-blue-600","hover:ring-blue-500","hover:text-blue-500","primary","md","https://serversideup.net/open-source/spin/","i-lucide-arrow-right","outline",[812,813,814],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":10,"searchDepth":380,"depth":380,"links":816},[817,818,819,820,821],{"id":244,"depth":380,"text":245},{"id":267,"depth":380,"text":268},{"id":286,"depth":380,"text":287},{"id":349,"depth":380,"text":350},{"id":789,"depth":380,"text":790},"Eliminate the headache of file permissions when working with PHP + Docker.",null,{"head":825,"layout":7},{"title":826},"Understanding file permissions - Docker PHP - Server Side Up",{"title":171,"description":822},"EsxUj7y-ZVCDsKYikvjLI4Wa7l-hSZpasp2sayqjaHc",[830,832],{"title":167,"path":168,"stem":169,"description":831,"children":-1},"Learn about S6 Overlay and how it is used in this project.",{"title":175,"path":176,"stem":177,"description":833,"children":-1},"Learn how to configure trusted proxies to get accurate client IP addresses when running behind load balancers, CDNs, or reverse proxies.",1776367057923]