# Docker
ApisCP does not yet provide direct support for Docker, but it's easy to integrate into ApisCP.
# Installation
Unsafe for multi-tenant operations
The instructions contained herein assume you are the only person managing accounts on this server.
# CentOS 7
Install the docker
package using yum
, then replicate it into the filesystem template.
yum install -y docker
/usr/local/apnscp/bin/scripts/yum-post.php install -d docker siteinfo
mkdir -p /home/virtual/FILESYSTEMTEMPLATE/siteinfo/etc/sysconfig
cp -dp /etc/sysconfig/docker /home/virtual/FILESYSTEMTEMPLATE/siteinfo/etc/sysconfig/
systemctl reload fsmount
Files in /etc/sysconfig
are typically skipped during filesystem replication. Populate this file to avoid any complications during initialization.
CentOS 7 rhsm hotfix
Broken package dependencies create a circular link in CentOS 7 for RedHat's subscription manager, required by Docker. Remove the dangling link, then download the CA/intermediate certs directly from RedHat (CentOS #14785 (opens new window)):
rm -f /etc/rhsm/ca/redhat-uep.pem
openssl s_client -showcerts -servername registry.access.redhat.com -connect registry.access.redhat.com:443 </dev/null 2>/dev/null | openssl x509 -text > /etc/rhsm/ca/redhat-uep.pem
cp -fpldR /etc/rhsm /home/virtual/FILESYSTEMTEMPLATE/siteinfo/etc/
# CentOS 8/Stream
docker-ce
package replaces docker
in CentOS 8. CentOS 8 replaces Docker with Podman (opens new window) as a noteworthy alternative. To run Docker on a CentOS 8+ platform, Cockpit must be removed as well as an additional repository added that contains docker-ce
and dependencies.
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y --allowerasing docker-ce
/usr/local/apnscp/bin/scripts/yum-post.php install -d docker-ce siteinfo
systemctl reload fsmount
Docker's default configuration inhibits listening in other locations. It will be overrode with a custom systemd service definition that omits -H fd://
(see #22339 (opens new window), #21559 (opens new window), #25471 (opens new window), PR#27473 (opens new window)).
mkdir -p /etc/systemd/system/docker.service.d
cat << EOF > /etc/systemd/system/docker.service.d/override.conf
[Service]
# This clears any ExecStart= inherited from docker.service
ExecStart=
ExecStart=/usr/bin/dockerd --containerd=/run/containerd/containerd.sock
EOF
systemctl daemon-reload
# Final setup
Add a group named docker
. This group will be used to authorize any user to use the Docker service. We'll come back to this later, adding the group for each site that will have Docker access.
groupadd --system docker 2> /dev/null
Reconfigure Docker to expose its Unix socket to an accessible location within the filesystem template.
systemctl enable --now docker
echo -e '{\n\t"hosts": ["unix:///var/run/docker.sock", "unix:///.socket/docker.sock"],\n\t"group": "docker"\n}' > /etc/docker/daemon.json
systemctl restart docker
ln -s /.socket/docker.sock /home/virtual/FILESYSTEMTEMPLATE/siteinfo/var/run/docker.sock
systemctl reload fsmount
# Authorizing Docker usage per site
For each site to enable Docker, create the group then add the Site Administrator (or any sub-user) to the docker
group.
Assume we're adding the Site Administrator for apiscp.com to use Docker. get_config
, a CLI helper, will help lookup the Site Administrator (admin_user
field) for a given site.
chroot /home/virtual/apiscp.com/ groupadd --system -g "$(getent group docker | cut -d: -f3)" docker
chroot /home/virtual/apiscp.com/ usermod -G docker -a "$(get_config apiscp.com siteinfo admin_user)"
# Switch to the Site Admin for apiscp.com
su apiscp.com
# Confirm the user is part of "docker" group
id
# Sample response:
# uid=9999(myadmin) gid=1000(myadmin) groups=1000(myadmin),10(wheel),978(docker)
# First container
For a hypothetical first run app, Apache Zeppelin (opens new window) is used as it's what initiated this document.
Create a subdomain or addon domain. Inside the document root (opens new window) add a .htaccess (opens new window) file that will proxy all requests to the container.
For example, if the port argument is --port 8082:8080
then externally, 8082 is being forwarded internally to the Docker container expecting traffic on 8080. Knowing this, prepare the .htaccess file:
DirectoryIndex disabled
RewriteEngine On
RewriteCond %{HTTP:Connection} =upgrade [NC]
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^(.*)$ ws://localhost:8082/$1 [L,QSA,P]
RewriteRule ^(.*)$ http://localhost:8082/$1 [L,QSA,P]
Websocket disabled by default
Enable Websocket support by loading the wstunnel (opens new window) module. Not every application utilizes Websockets, so check with your vendor documentation. Zeppelin requires Websocket for use.
Add LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
to /etc/httpd/conf/httpd-custom.conf
, then run htrebuild
to activate configuration changes.
Disabling index negotiation
Apache will try multiple files to determine which file to serve when no file is explicitly provided in the request URI. Disabling an index ensures this request - without a filename - is passed directly to your Docker instance for resolution.
Run Zeppelin, and you're done!
docker run -d -p 8082:8080 --name zeppelin apache/zeppelin:0.9.0
To list active containers, run docker container list
. To stop a container, run docker stop CONTAINER-ID
. Adding --rm
to the task creates a volatile container (opens new window), which will remove created data on exit.
This task may be added to boot on startup using the @reboot
token in crond:
crontab -e
# Next add the following line
@reboot docker run -d -p 8082:8080 --name zeppelin apache/zeppelin:0.9.0
# Container management
Portainer (opens new window) is a tool made for managing a collection of Docker containers. Given the sensitive nature of sending credentials across the wire, this subdomain (portainer.apiscp.com) will require HTTPS and set an HTTP strict transport security header to ensure future connections use SSL.
First create the persistent volume store, then run it.
docker volume create portainer_data
docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer
Lastly, connect it using Apache to the backend container.
DirectoryIndex disabled
RequestHeader set X-Forwarded-Proto "https"
Header always set Strict-Transport-Security "max-age=63072000;"
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R,L]
RewriteCond %{HTTP:Connection} =upgrade [NC]
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^(.*)$ ws://localhost:9000/$1 [L,QSA,P]
RewriteRule ^(.*)$ http://localhost:9000/$1 [L,QSA,P]
That's all there is!
# Adding docker-compose
docker-compose
is a tool (opens new window) for defining and running multi-container Docker applications. It requires Python 3.6+, which can be installed on a per-site basis.
su apiscp.com
# Compile Python 3.6.5
pyenv install 3.6.5
# Set Python 3.6.5 globally for this account
pyenv global 3.6.5
# Install docker-compose
pip install docker-compose
# Confirm it's installed
docker-compose -v
# Reports: "docker-compose version 1.28.2, build unknown"
# Security
This current implementation of Docker is not suitable in a multi-administrator environment. Users within other domains may see and manage Docker containers. There are plans to implement RBAC and bring Docker as a permanent fixture to ApisCP, but for now only one authorized user may use Docker on a server.
If the same group treatment is applied to another domain, for instance, that user also has visibility of the Docker containers:
chroot /home/virtual/apisnetworks.com/ groupadd --system -g "$(getent group docker | cut -d: -f3)" docker
chroot /home/virtual/apisnetworks.com/ usermod -G docker -a "$(get_config apisnetworks.com siteinfo admin_user)"
su apisnetworks.com
docker container ls
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# 13d1ed48ff42 portainer/portainer "/portainer" 8 hours ago Up 8 hours 0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp portainer
# f1a5e11a3cc9 apache/zeppelin:0.9.0 "/usr/bin/tini -- ..." 9 hours ago Up 9 hours 0.0.0.0:8082->8080/tcp zeppelin
Secondly, in the above examples, Docker privileges are bestowed to the Site Administrator of an account via usermod
. In normal configuration, PHP-FPM runs as a separate, unprivileged user (apache
). This feature may be changed by editing the service parameter apache
,webuser
to match the Site Administrator. In such configurations, an exploit in PHP would permit an attacker access to all docker
commands.
Never grant a PHP-FPM user Docker privileges. Never designate a Docker user as apache,webuser.
# NEVER EVER DO THIS!
EditDomain -c apache,webuser="$(get_config apisnetworks.com siteinfo admin_user)" apisnetworks.com
chroot /home/virtual/apisnetworks.com/ usermod -G docker -a "$(get_config apisnetworks.com siteinfo admin_user)"
# ^^^ BZZZT. WRONG. ^^^