Search code examples
apachedockerconfigvolume

httpd in docker cannot start


I'm trying to install HTTPD in docker, I wrote a dockerfile like this:

FROM centos

VOLUME /var/log/httpd
VOLUME /etc/httpd
VOLUME /var/www/html

# Update Yum Repostory
RUN yum clean all && \
    yum makecache fast && \
    yum -y update && \
    yum -y install httpd
RUN yum clean all
EXPOSE 80
CMD /usr/sbin/httpd -D BACKGROUND && tail -f /var/log/httpd/access_log

it works if I run the image without host volumes, but failed if I use parameter:

--volume /data/httpd/var/www/html:/var/www/html --volume /data/httpd/var/log:/var/log --volume /data/httpd/etc:/etc/httpd

the error message is:

httpd: Could not open configuration file /etc/httpd/conf/httpd.conf: No such file or directory

I checked the mount point which is empty:

# ll /data/httpd/etc/
total 0

But if I don't use "volume" by default docker copys over files to a temp folder:

# ll /var/lib/docker/volumes/04f083887e503c6138a65b300a1b40602d227bb2bbb58c69b700f6ac753d1c34/_data
total 4
drwxr-xr-x. 2 root root   35 Nov  3 03:16 conf
drwxr-xr-x. 2 root root   78 Nov  3 03:16 conf.d
drwxr-xr-x. 2 root root 4096 Nov  3 03:16 conf.modules.d
lrwxrwxrwx. 1 root root   19 Nov  3 03:16 logs -> ../../var/log/httpd
lrwxrwxrwx. 1 root root   29 Nov  3 03:16 modules -> ../../usr/lib64/httpd/modules
lrwxrwxrwx. 1 root root   10 Nov  3 03:16 run -> /run/httpd

So I'm confused, why docker refused to copy them to the named location? and how to fix this problem?


Solution

  • This is a documented behavior indeed:

    Volumes are initialized when a container is created. If the container’s base image contains data at the specified mount point, that existing data is copied into the new volume upon volume initialization. (Note that this does not apply when mounting a host directory.)

    i.e. when you mount the /etc/httpd volume --volume /data/httpd/etc:/etc/httpd, no data will be copied.

    You can also see https://github.com/docker/docker/pull/9092 for a more detailed discussion on why it works this way (in case you are interested).

    A usual workaround for this is to copy your initial data, to the volume folder (from within the container), inside your ENTRYPOINT or CMD script, in case it is empty.

    Note that your initial dataset must be kept outside the volume folder (e.g. as .tar file in /opt), for this to work, as the volume folder will be shadowed by the host folder mounted over it.

    Given below is a sample Dockerfile and Script, which demonstrate the behavior:

    Sample Dockerfile

    FROM debian:stable
    RUN mkdir -p /opt/test/; touch /opt/test/initial-data-file
    VOLUME /opt/test
    

    Sample script (try various volume mappings)

    #Build image
    >docker build -t volumetest .
    
    Sending build context to Docker daemon  2.56 kB
    Step 0 : FROM debian:stable
    ---> f7504c16316c
    Step 1 : RUN mkdir -p /opt/test/; touch /opt/test/initial-data-file
    ---> Using cache
    ---> 1ea0475e1a18    
    Step 2 : VOLUME /opt/test
    ---> Using cache
    ---> d8d32d849b82
    Successfully built d8d32d849b82
    
    #Implicit Volume mapping (as defined in Dockerfile)
    >docker run --rm=true volumetest ls -l /opt/test
    total 0
    -rw-r--r-- 1 root root 0 Nov  4 18:26 initial-data-file
    
    #Explicit Volume mapping
    > docker run --rm=true --volume /opt/test volumetest ls -l /opt/test/
    total 0
    -rw-r--r-- 1 root root 0 Nov  4 18:26 initial-data-file
    
    #Explicitly Mounted Volume
    >mkdir test
    >docker run --rm=true --volume "$(pwd)/test/:/opt/test" volumetest ls -l /opt/test
    total 0
    

    And here is a simple entrypoint script, illustrating a possible workaround:

    #!/bin/bash
    VOLUME=/opt/test
    DATA=/opt/data-volume.tar.gz
    
    if [[ -n $(find "$VOLUME" -maxdepth 0 -empty) ]]
    then
        echo Preseeding VOLUME $VOLUME with data from $DATA...
        tar -C "$VOLUME" -xvf "$DATA" 
    fi
    
    "$@"
    

    add the following to the Dockerfile

    COPY data-volume.tar.gz entrypoint /opt/
    ENTRYPOINT ["/opt/entrypoint"]
    

    First run:

    >docker run --rm=true --volume "$(pwd)/test/:/opt/test" volumetest ls -l /opt/test
    Preseeding VOLUME /opt/test with data from /opt/data-volume.tar.gz...
    preseeded-data
    total 0
    -rw-r--r-- 1 1001 users 0 Nov  4 18:43 preseeded-data
    

    Subsequent runs:

    >docker run --rm=true --volume "$(pwd)/test/:/opt/test" volumetest ls -l /opt/test
    ls -l /opt/test
    total 0
    -rw-r--r-- 1 1001 users 0 Nov  4 18:43 preseeded-data
    

    Note, that the volume folder will only be populated with data, if it was completely empty before.