gliderlabs / herokuish

Utility for emulating Heroku build and runtime tasks in containers
MIT License
1.44k stars 150 forks source link

Dokku deployment fails with PHP buildpack as php-cli.ini write premission is denied at runtime #1212

Closed pcky closed 4 months ago

pcky commented 5 months ago

With Dokku using 'herokuish: v0.9.1' the heroku PHP buildpack is building an image that fails because at runtime the php-cli.ini write premission is denied:

/app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied

php-cli.ini is owned by root so probably thats the issue.

josegonzalez commented 5 months ago

What version of dokku are you running?

pcky commented 5 months ago

@josegonzalez dokku version 0.34.4

josegonzalez commented 5 months ago

That 100-php.sh file isn't in your repository, correct?

josegonzalez commented 5 months ago

Is there anything special about this app? Do you have a custom php-cli.ini?

pcky commented 5 months ago

That 100-php.sh file isn't in your repository, correct? correct.

pcky commented 5 months ago

Is there anything special about this app? Do you have a custom php-cli.ini?

Nothing special i just cloned the heroku php-getting-started repo:

user@mail2:~$ git clone https://github.com/heroku/php-getting-started.git
Cloning into 'php-getting-started'...
remote: Enumerating objects: 231, done.
remote: Counting objects: 100% (41/41), done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 231 (delta 16), reused 28 (delta 9), pack-reused 190
Receiving objects: 100% (231/231), 73.39 KiB | 12.23 MiB/s, done.
Resolving deltas: 100% (95/95), done.
user@mail2:~$ cd php-getting-started/
user@mail2:~/php-getting-started$ git push [dokku@example.com]:php-getting-started
Enter passphrase for key '/home/user/.ssh/id_rsa': 
Enumerating objects: 215, done.
Counting objects: 100% (215/215), done.
Delta compression using up to 4 threads
Compressing objects: 100% (114/114), done.
Writing objects: 100% (215/215), 70.36 KiB | 70.36 MiB/s, done.
Total 215 (delta 85), reused 213 (delta 84)
remote: Resolving deltas: 100% (85/85), done.
-----> Set main as deploy-branch
-----> Cleaning up...
-----> Building php-getting-started from herokuish
-----> Adding BUILD_ENV to build environment...
       BUILD_ENV added successfully
-----> PHP app detected
remote: -----> Bootstrapping...
remote: -----> Preparing platform package installation...
remote:        NOTICE: No runtime required in composer.json; requirements
remote:        from dependencies in composer.lock will be used for selection
remote: -----> Installing platform packages...
remote:        - php (8.3.5)
remote:        - apache (2.4.59)
remote:        - composer (2.7.2)
remote:        - nginx (1.24.0)
remote: -----> Installing dependencies...
remote:        Composer version 2.7.2 2024-03-11 17:12:18
remote:        Installing dependencies from lock file
remote:        Verifying lock file contents can be installed on current platform.
remote:        Package operations: 22 installs, 0 updates, 0 removals
remote:          - Downloading laravel/serializable-closure (v1.3.0)
remote:          - Downloading psr/log (3.0.0)
remote:          - Downloading monolog/monolog (3.3.1)
remote:          - Downloading nikic/fast-route (v1.3.0)
remote:          - Downloading psr/http-message (1.0.1)
remote:          - Downloading psr/http-server-handler (1.0.1)
remote:          - Downloading psr/http-server-middleware (1.0.1)
remote:          - Downloading psr/http-factory (1.0.1)
remote:          - Downloading psr/container (2.0.2)
remote:          - Downloading slim/slim (4.11.0)
remote:          - Downloading php-di/invoker (2.3.3)
remote:          - Downloading php-di/php-di (7.0.2)
remote:          - Downloading php-di/slim-bridge (3.3.0)
remote:          - Downloading symfony/polyfill-php80 (v1.27.0)
remote:          - Downloading ralouphie/getallheaders (3.0.3)
remote:          - Downloading fig/http-message-util (1.1.5)
remote:          - Downloading slim/psr7 (1.6)
remote:          - Downloading symfony/polyfill-mbstring (v1.27.0)
remote:          - Downloading symfony/polyfill-ctype (v1.27.0)
remote:          - Downloading twig/twig (v3.5.1)
remote:          - Downloading symfony/polyfill-php81 (v1.27.0)
remote:          - Downloading slim/twig-view (3.3.0)
remote:          - Installing laravel/serializable-closure (v1.3.0): Extracting archive
remote:          - Installing psr/log (3.0.0): Extracting archive
remote:          - Installing monolog/monolog (3.3.1): Extracting archive
remote:          - Installing nikic/fast-route (v1.3.0): Extracting archive
remote:          - Installing psr/http-message (1.0.1): Extracting archive
remote:          - Installing psr/http-server-handler (1.0.1): Extracting archive
remote:          - Installing psr/http-server-middleware (1.0.1): Extracting archive
remote:          - Installing psr/http-factory (1.0.1): Extracting archive
remote:          - Installing psr/container (2.0.2): Extracting archive
remote:          - Installing slim/slim (4.11.0): Extracting archive
remote:          - Installing php-di/invoker (2.3.3): Extracting archive
remote:          - Installing php-di/php-di (7.0.2): Extracting archive
remote:          - Installing php-di/slim-bridge (3.3.0): Extracting archive
remote:          - Installing symfony/polyfill-php80 (v1.27.0): Extracting archive
remote:          - Installing ralouphie/getallheaders (3.0.3): Extracting archive
remote:          - Installing fig/http-message-util (1.1.5): Extracting archive
remote:          - Installing slim/psr7 (1.6): Extracting archive
remote:          - Installing symfony/polyfill-mbstring (v1.27.0): Extracting archive
remote:          - Installing symfony/polyfill-ctype (v1.27.0): Extracting archive
remote:          - Installing twig/twig (v3.5.1): Extracting archive
remote:          - Installing symfony/polyfill-php81 (v1.27.0): Extracting archive
remote:          - Installing slim/twig-view (3.3.0): Extracting archive
remote:        Generating optimized autoload files
remote:        9 packages you are using are looking for funding.
remote:        Use the `composer fund` command to find out more!
remote: -----> Preparing runtime environment...
remote: -----> Checking for additional extensions to install...
-----> Discovering process types
       Procfile declares types -> web
-----> Releasing php-getting-started...
-----> Checking for predeploy task
       No predeploy task found, skipping
-----> Checking for release task
       No release task found, skipping
-----> Checking for first deploy postdeploy task
       No first deploy postdeploy task found, skipping
=====> Processing deployment checks
remote:  !     No healthchecks found in app.json for web process type                          
       No web healthchecks found in app.json. Simple container checks will be performed.
       For more efficient zero downtime deployments, add healthchecks to your app.json. See https://dokku.com/docs/deployment/zero-downtime-deploys/ for examples
-----> Deploying php-getting-started via the docker-local scheduler...
-----> Deploying web (count=1)
       Attempting pre-flight checks (web.1)
-----> Executing 2 healthchecks                                                        
       Running healthcheck name='default' type='uptime' uptime=10                      
       Running healthcheck name='port listening check' attempts=3 port=5000 retries=2 timeout=5 type='listening' wait=5
remote:  !     Failure in name='default': container has restarted 7 times                      
=====> Start healthcheck output                                                        
       state=restarting                                                                
=====> End healthcheck output                                                          
remote:  !     Failure in name='port listening check': container state is not running          
745ed46b7724749bbc849205d7108de62c5f5cf1ea5a7e402269da6ffe83bb1d
remote:  !     Could not start due to 1 failed checks (web.1)
=====> Start of php-getting-started container output (web.1)
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
=====> End of php-getting-started container output (web.1)
=====> Start of  container output (.)
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
       /app/.profile.d/100-php.sh: line 19: /app/.heroku/php/etc/php/php-cli.ini: Permission denied
=====> End of  container output (.)
remote: parallel: This job failed:
remote: /var/lib/dokku/plugins/available/scheduler-docker-local/bin/scheduler-deploy-process-container php-getting-started herokuish dokku/php-getting-started:latest latest web 1 1
To example.com ]:php-getting-started
 ! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to '[dokku@example.com]:php-getting-started'
josegonzalez commented 5 months ago

Got it, thats good enough for me to try and replicate this/see whats going on :)

mykolasolodukha commented 5 months ago

@josegonzalez FYI

It looks like the dokku (running container?) cannot get access to the files created during deployment.

For example, I run a Python application and I have a .profile file like this:

pybabel compile --directory ./locales/

This creates a message.mo file in the ./locales/uk/LC_MESSAGES directory. The problem (apparently) is that this file is getting created with the root user ownership, which then makes it impossible to access the file when the container is being run from the herokuish user.


image
mykolasolodukha commented 5 months ago

P.S. Likely related to https://github.com/dokku/dokku/pull/6791#diff-34c5fe47163a293df72af11867926c1a95a7be4f50006ca378e40ebf2249c4f2R6

mykolasolodukha commented 5 months ago

P.P.S. After downgrading to 0.34.3, turns out it never runs the .profile file during the build phase, only when it actually tries to run the container.


Here's the screenshot of the Docker image built (run with docker run --rm -it ... bash):

image
aaronfc commented 4 months ago

The original error is still happening to me as of today with dokku.

To fix it I did a downgrade to dokku 0.34.3 with sudo apt install dokku=0.34.3.

After that, dokku ps:rebuild <my-app> worked fine :)

josegonzalez commented 4 months ago

Here is the problem:

remote: #7 [3/3] RUN TRACE=$TRACE USER=herokuishuser HEROKUISH_DISABLE_CHOWN=false /exec true
remote: #7 0.811 Do not run Composer as root/super user! See https://getcomposer.org/root for details

We run /exec true there to simulate a chown of files, but then I think somehow composer gets involved - I think because of this .profile.d file- which then writes a bunch of files as root.

So basically when we chown, the .profile files are executed. The fix here is to do the chown directly instead of relying on Herokuish to perform it, which will ensure the .profile files are only executed at runtime and not during build.

Effectively, we want something like the following in builder-release.Dockerfile within the Dokku repo:

find "$app_path" \( \! -user "$unprivileged_user" -o \! -group "$unprivileged_group" \) -print0 | xargs -0 -r chown "$unprivileged_user:$unprivileged_group"

Replacing the variables appropriately.