Search code examples
dockergithubsshcomposer-php

Using PHP composer in docker container for private repositories with SSH


I need to run php composer in a docker utility container as a part of setting up a PHP development environment.

I am using the official composer docker image, and following steps in the dockerhub documentation.

  • I need to run composer install as current local user so that the vendor directory created with same permissions.
  • I have several private dependencies in github authorised via SSH, which composer just grabs without any issues if I use a local installation.

It seems to be a common requirement and a ready solution available in the doc

eval $(ssh-agent); \
docker run --rm --interactive --tty \
  --volume $PWD:/app \
  --volume $SSH_AUTH_SOCK:/ssh-auth.sock \
  --volume /etc/passwd:/etc/passwd:ro \
  --volume /etc/group:/etc/group:ro \
  --env SSH_AUTH_SOCK=/ssh-auth.sock \
  --user $(id -u):$(id -g) \
  composer install

But it did not works for me and composer asking for a github token (Running Docker version 26.0.0 from Ubuntu 23).

The command looks good for me as it binds the SSH_SOCKET to the container and also the /etc/passwd and /etc/group files.

But why does the SSH agent forward doesn't works as expected?

What I've tried so far,

  • I've tried to check the SSH connection to github with docker command sh -c "ssh -T git@github.com" instead of install - That gives error Could not create directory '/home/<user>/.ssh' and permission denied to github.

  • I've tried to add my SSH key to ssh-agent and run the docker command, but received same error (also with composer install command)

    eval $(ssh-agent -s) && \
    ssh-add ~/.ssh/<my_private_key> && \
    docker run --rm --interactive --tty \
     --volume $PWD:/app \
     --volume $SSH_AUTH_SOCK:/ssh-auth.sock \
     --volume /etc/passwd:/etc/passwd:ro \
     --volume /etc/group:/etc/group:ro \
     --env SSH_AUTH_SOCK=/ssh-auth.sock \
     --user $(id -u):$(id -g) \
     composer \
     sh -c "ssh -T git@github.com"
    
  • Tried to bind my system SSH keys to container as a next solution, but I don't know how to do that correctly, composer uses alpine linux base image, and I think the default location SSH looks for keys is /home/<user>/.ssh, I ve tried creating users and home directory using a Dockerfile but that also failed.

  • The last resort is to use a github token for pulling private composer dependencies, but is there a way to do this with SSH?


Solution

  • Answering my own question,

    The issue was with the ssh-add command,

    eval $(ssh-agent); \
    docker run --rm --interactive --tty \
      ...
    

    This alone did not works and I need to add my ssh key after the ssh-agent initialization like this,

    eval $(ssh-agent -s) && \
    ssh-add ~/.ssh/id_rsa && \
     docker run --rm --interactive --tty \
     ...
    

    (Note: This was already mentioned in the question, but It was a mistake that I got multiple ssh keys and provided the wrong one that time.

    Also made some changes from the documentation mentioned approach that is mentioned below)

    This is not an elegant way to run composer like this (as suggested by @david-maze in the question comments), but if your requirement is to keep everything in isolated containers, you can check this one.

    So need to run this long command every time I need to do a composer install, which is not convenient and also there is no caching (packages will be downloaded every time) also there is this ssh known-host prompt that appears every time you are connecting to github to pull a private repo.

    The authenticity of host 'github.com (20.111.11.82)' can't be established.
    ECDSA key fingerprint is SHA256:XXXXX.
    Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
    

    And in my case, I am using this in a development environment setup, so I can create a user with a Dockerfile at the setup time so that the /etc/passwd and /etc/group bindings can be removed,

    So created a docker compose configuration like this,

      composer:
        build:
          context: .
          dockerfile: ./dockerfiles/composer.dockerfile
          args:
            USER_ID: ${C_UID}
            GROUP_ID: ${C_GID}
            UNAME: ${C_USER}
        container_name: util-composer
        user: "${C_UID}:${C_GID}"
        # For the container
        environment:
          SSH_AUTH_SOCK: "/ssh-auth.sock"
        volumes:
          - "$PWD:/app"
          - "util_composer_cache:/tmp"
          # docker host ssh forwarding
          - "$SSH_AUTH_SOCK:/ssh-auth.sock"
        command: "update --no-progress --no-interaction --ignore-platform-reqs"
        stdin_open: true
        tty: true
        networks:
          - my-network
    
    • --ignore-platform-reqs is required as the source code php dependent libraries will not be there in your composer utility container.
    • util_composer_cache:/tmp - a named volume is attached to /tmp which is the path composer stores cache.

    This is the Dockerfile.

    FROM composer
    
    ARG USER_ID=1000
    ARG GROUP_ID=1000
    ARG UNAME=nonrootuser
    
    RUN addgroup -g $GROUP_ID $UNAME \
      && adduser -G $UNAME -g $UNAME -s /bin/sh -u $USER_ID -D $UNAME \
      && mkdir -p /home/$UNAME/.ssh \
      && chmod 700 /home/$UNAME/.ssh \
      && chown $USER_ID:$GROUP_ID /home/$UNAME/.ssh \
      && su - $UNAME -c "ssh-keyscan github.com >> /home/$UNAME/.ssh/known_hosts"
    
    • Which adds github to known_hosts list

    Now run command like this for the default command,

    eval $(ssh-agent -s) && \
    ssh-add ~/.ssh/id_rsa && \
    C_USER=$(whoami) C_UID=$(id -u) C_GID=$(id -g) docker compose run --rm composer
    
    • Need to set the User related variables at runtime.

    Now at last to simplify the command - Add this in ~/.zshrc/~/.bashrc to use as alias

    composer-util() {
      eval $(ssh-agent -s)
      ssh-add "${HOME}/.ssh/id_rsa"
      C_USER=$(whoami) C_UID=$(id -u) C_GID=$(id -g) docker compose run --rm composer "$@"
    }
    

    finally, use like this,

    # This will run the default command - "update --no-progress --no-interaction --ignore-platform-reqs" in my case
    composer-util
    
    # but can be override with any composer command (as the composer is the ENTRYPOINT set)
    composer-util dump-autoload -o