├── .dockerignore ├── Dockerfile-backup ├── Dockerfile-ceph ├── Dockerfile-consul ├── Dockerfile-data ├── Dockerfile-haproxy ├── Dockerfile-logrotate ├── Dockerfile-mariadb ├── Dockerfile-nginx ├── Dockerfile-php-fpm ├── Dockerfile-phpmyadmin ├── Dockerfile-restore ├── Dockerfile-ssh ├── build_all.sh ├── ceph └── webserver-entrypoint.sh ├── consul ├── config.json └── webserver-entrypoint.sh ├── docs └── advanced.md ├── haproxy ├── haproxy.conf └── webserver-entrypoint.sh ├── hooks └── post_push ├── license.txt ├── logrotate └── logrotate.conf ├── mariadb ├── galera.cnf └── webserver-entrypoint.sh ├── nginx └── webserver-entrypoint.sh ├── php-fpm └── webserver-entrypoint.sh ├── readme.md ├── ssh └── webserver-entrypoint.sh └── webserver-common ├── apt-get-cleanup.sh ├── apt-get-install-ceph-fuse.sh ├── apt-get-install-common.sh ├── apt-get-update.sh ├── ceph-mount.sh ├── create-git-user.sh ├── determine-service-master-node.sh ├── get-own-ip.sh ├── list-service-nodes.sh ├── readme.md └── wait-for-dns-update.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | webserver-common/readme.md 2 | -------------------------------------------------------------------------------- /Dockerfile-backup: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | CMD \ 5 | 6 | # Save all mounted volumes into tar file, name of which was specified by BACKUP_FILENAME environment variable 7 | 8 | tar -cf "/backup/$BACKUP_FILENAME.tar" $( \ 9 | mount | \ 10 | grep --invert-match --perl-regexp ' on /(dev|proc|sys|\s)' | \ 11 | grep --invert-match --perl-regexp '/backup|/etc/(hostname|hosts|resolv.conf)' | \ 12 | awk '{ print $3 }' \ 13 | ) 14 | -------------------------------------------------------------------------------- /Dockerfile-ceph: -------------------------------------------------------------------------------- 1 | FROM ceph/daemon:tag-build-master-kraken-ubuntu-16.04 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | COPY webserver-common /webserver-common/ 5 | 6 | RUN \ 7 | 8 | /webserver-common/apt-get-update.sh && \ 9 | /webserver-common/apt-get-install-common.sh && \ 10 | /webserver-common/apt-get-cleanup.sh 11 | 12 | COPY ceph/webserver-entrypoint.sh / 13 | 14 | VOLUME /var/lib/ceph 15 | 16 | ENV \ 17 | CONSUL_SERVICE=consul \ 18 | 19 | CLUSTER=ceph \ 20 | KV_TYPE=consul \ 21 | KV_IP=consul \ 22 | KV_PORT=80 \ 23 | OSD_TYPE=directory \ 24 | CEPHFS_CREATE=1 \ 25 | CEPH_MON_SERVICE=ceph-mon 26 | 27 | ENTRYPOINT ["/webserver-entrypoint.sh"] 28 | -------------------------------------------------------------------------------- /Dockerfile-consul: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | COPY webserver-common /webserver-common/ 5 | 6 | RUN \ 7 | 8 | /webserver-common/apt-get-update.sh && \ 9 | /webserver-common/apt-get-install-common.sh && \ 10 | 11 | CONSUL_VERSION=1.0.3 && \ 12 | 13 | apt-get install -y --no-install-recommends unzip && \ 14 | 15 | curl -o /tmp/consul.zip https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip && \ 16 | unzip -d /bin /tmp/consul.zip && \ 17 | rm /tmp/consul.zip && \ 18 | 19 | apt-get purge -y unzip && \ 20 | 21 | /webserver-common/apt-get-cleanup.sh && \ 22 | 23 | mkdir /etc/consul.d 24 | 25 | COPY consul/config.json /etc/consul.d/config.json 26 | COPY consul/webserver-entrypoint.sh / 27 | 28 | # /tmp will be used for non-master nodes to store data between restarts and image upgrades 29 | VOLUME \ 30 | /var/lib/consul 31 | 32 | ENV \ 33 | SERVICE_NAME=consul \ 34 | MIN_SERVERS=3 \ 35 | GOMAXPROCS=2 36 | 37 | ENTRYPOINT ["/webserver-entrypoint.sh"] 38 | -------------------------------------------------------------------------------- /Dockerfile-data: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | RUN \ 5 | 6 | mkdir -p \ 7 | /data \ 8 | /etc/ssh 9 | 10 | VOLUME \ 11 | /data \ 12 | /etc/ssh 13 | -------------------------------------------------------------------------------- /Dockerfile-haproxy: -------------------------------------------------------------------------------- 1 | FROM haproxy:1 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | COPY webserver-common /webserver-common/ 5 | 6 | RUN \ 7 | 8 | /webserver-common/apt-get-update.sh && \ 9 | /webserver-common/apt-get-install-common.sh && \ 10 | /webserver-common/apt-get-cleanup.sh 11 | 12 | 13 | COPY haproxy/haproxy.conf /usr/local/etc/haproxy/haproxy.conf.dist 14 | COPY haproxy/webserver-entrypoint.sh / 15 | 16 | ENTRYPOINT ["/webserver-entrypoint.sh"] 17 | -------------------------------------------------------------------------------- /Dockerfile-logrotate: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | COPY webserver-common /webserver-common/ 5 | 6 | RUN \ 7 | 8 | /webserver-common/apt-get-update.sh && \ 9 | 10 | apt-get install -y --no-install-recommends logrotate && \ 11 | 12 | /webserver-common/apt-get-cleanup.sh 13 | 14 | COPY logrotate/logrotate.conf /etc/logrotate.conf 15 | 16 | ENV TERM=xterm 17 | 18 | # Run logrotate every hour 19 | 20 | CMD watch --no-title --interval 3600 logrotate /etc/logrotate.conf 21 | -------------------------------------------------------------------------------- /Dockerfile-mariadb: -------------------------------------------------------------------------------- 1 | FROM mariadb:10.1 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | COPY webserver-common /webserver-common/ 5 | 6 | RUN \ 7 | 8 | /webserver-common/create-git-user.sh && \ 9 | 10 | /webserver-common/apt-get-update.sh && \ 11 | /webserver-common/apt-get-install-common.sh && \ 12 | /webserver-common/apt-get-install-ceph-fuse.sh && \ 13 | /webserver-common/apt-get-cleanup.sh && \ 14 | 15 | # We'll keep configs in /data/mysql/config on first instance and local directory on others (is set locally in container) 16 | 17 | sed -i 's/\/etc\/mysql/\/data\/mysql\/config/g' /etc/mysql/my.cnf && \ 18 | 19 | # Append Galera cluster config inclusion to default config 20 | 21 | echo '!include /data/mysql/config/galera.cfg' >> /etc/mysql/my.cnf && \ 22 | 23 | # We'll keep data in /data/mysql/data on first instance and local directory on others (is set locally in container) 24 | 25 | sed -i 's/\/var\/lib\/mysql/\/data\/mysql\/data/g' /etc/mysql/my.cnf && \ 26 | 27 | # We'll keep logs in /data/mysql/log on first instance and local directory on others (is set locally in container) 28 | 29 | sed -i 's/\/var\/log\/mysql/\/data\/mysql\/log/g' /etc/mysql/my.cnf && \ 30 | 31 | # This is to redirect logs to stderr instead of non-running syslog (otherwise error messages will be lost) 32 | 33 | truncate --size=0 /etc/mysql/conf.d/mysqld_safe_syslog.cnf && \ 34 | 35 | mv /etc/mysql /etc/mysql_dist && \ 36 | 37 | # Copy original entrypoint without exec call in order to use it as MariaDB initialization script 38 | 39 | sed 's/exec "$@"//g' /usr/local/bin/docker-entrypoint.sh > /docker-entrypoint-init.sh && \ 40 | chmod +x /docker-entrypoint-init.sh && \ 41 | 42 | # Restore original ids of mysql user and group, they will replace existing mysql group from the image, which were changed to custom 43 | 44 | userdel mysql && \ 45 | addgroup -gid 999 mysql && \ 46 | useradd -s /bin/bash -g 999 -u 999 mysql 47 | 48 | COPY mariadb/galera.cnf /etc/mysql_dist/galera.cfg 49 | COPY mariadb/webserver-entrypoint.sh / 50 | 51 | # /tmp will be used for non-master nodes to store data between restarts and image upgrades 52 | VOLUME \ 53 | /data \ 54 | /tmp 55 | 56 | ENV \ 57 | CEPH_MON_SERVICE=ceph-mon \ 58 | CEPHFS_MOUNT=0 \ 59 | 60 | SERVICE_NAME=mariadb 61 | 62 | ENTRYPOINT ["/webserver-entrypoint.sh"] 63 | 64 | CMD ["mysqld"] 65 | -------------------------------------------------------------------------------- /Dockerfile-nginx: -------------------------------------------------------------------------------- 1 | FROM nginx:1 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | COPY webserver-common /webserver-common/ 5 | 6 | RUN \ 7 | 8 | /webserver-common/create-git-user.sh && \ 9 | 10 | /webserver-common/apt-get-update.sh && \ 11 | /webserver-common/apt-get-install-common.sh && \ 12 | /webserver-common/apt-get-install-ceph-fuse.sh && \ 13 | /webserver-common/apt-get-cleanup.sh && \ 14 | 15 | # Run in foreground (let's specify it config file, since dealing with parameters that contain spaces is more difficult than we'd like it to be) 16 | 17 | echo "daemon off;\n$(cat /etc/nginx/nginx.conf)" > /etc/nginx/nginx.conf && \ 18 | 19 | # Change Nginx user 20 | 21 | sed -i 's/user nginx;/user git;/g' /etc/nginx/nginx.conf && \ 22 | 23 | # Make Nginx work with PHP with default config 24 | 25 | sed -i 's/127.0.0.1:9000/php:9000/g' /etc/nginx/conf.d/default.conf && \ 26 | sed -i 's/\/scripts$fastcgi_script_name/$document_root$fastcgi_script_name/g' /etc/nginx/conf.d/default.conf && \ 27 | 28 | # We'll keep configs in /data/nginx/config 29 | 30 | sed -i 's/\/etc\/nginx/\/data\/nginx\/config/g' `find /etc/nginx -type f` && \ 31 | 32 | # We'll keep logs in /data/nginx/log if CephFS is not used and local directory otherwise 33 | 34 | sed -i 's/\/var\/log\/nginx/\/data\/nginx\/log/g' `find /etc/nginx -type f` && \ 35 | 36 | # We'll keep data in /data/nginx/www 37 | 38 | sed -i 's/\/usr\/share\/nginx\/html/\/data\/nginx\/www/g' `find /etc/nginx -type f` && \ 39 | sed -i 's/root html/root \/data\/nginx\/www/g' /etc/nginx/conf.d/default.conf && \ 40 | 41 | mv /etc/nginx /etc/nginx_dist 42 | 43 | COPY nginx/webserver-entrypoint.sh / 44 | 45 | VOLUME \ 46 | /data \ 47 | /tmp 48 | 49 | ENV \ 50 | CEPH_MON_SERVICE=ceph-mon \ 51 | CEPHFS_MOUNT=0 52 | 53 | ENTRYPOINT ["/webserver-entrypoint.sh"] 54 | 55 | CMD ["nginx"] 56 | -------------------------------------------------------------------------------- /Dockerfile-php-fpm: -------------------------------------------------------------------------------- 1 | FROM nazarpc/php:fpm 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | COPY webserver-common /webserver-common/ 5 | 6 | RUN \ 7 | 8 | /webserver-common/create-git-user.sh && \ 9 | 10 | /webserver-common/apt-get-update.sh && \ 11 | /webserver-common/apt-get-install-common.sh && \ 12 | /webserver-common/apt-get-install-ceph-fuse.sh && \ 13 | /webserver-common/apt-get-cleanup.sh && \ 14 | 15 | # Change PHP-FPM user 16 | 17 | sed -i 's/www-data/git/g' /usr/local/etc/php-fpm.d/www.conf && \ 18 | 19 | # We'll keep configs in /data/php/config 20 | 21 | sed -i 's/etc\/php-fpm.d/php-fpm.d/g' /usr/local/etc/php-fpm.conf && \ 22 | 23 | mv /usr/local/etc /usr/local/etc_dist 24 | 25 | COPY php-fpm/webserver-entrypoint.sh / 26 | 27 | VOLUME \ 28 | /data 29 | 30 | # We're changing directory for configs, so this variables will help PHP to find its modules 31 | 32 | ENV \ 33 | PHP_INI_DIR=/data/php/config \ 34 | PHP_INI_SCAN_DIR=/data/php/config/php/conf.d 35 | 36 | ENV \ 37 | CEPH_MON_SERVICE=ceph-mon \ 38 | CEPHFS_MOUNT=0 39 | 40 | ENTRYPOINT ["/webserver-entrypoint.sh"] 41 | 42 | CMD ["php-fpm"] 43 | -------------------------------------------------------------------------------- /Dockerfile-phpmyadmin: -------------------------------------------------------------------------------- 1 | FROM nazarpc/phpmyadmin 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | -------------------------------------------------------------------------------- /Dockerfile-restore: -------------------------------------------------------------------------------- 1 | FROM debian:jessie 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | # Restore files from tar into root of the filesystem 5 | 6 | CMD tar -xf /backup.tar -C / 7 | -------------------------------------------------------------------------------- /Dockerfile-ssh: -------------------------------------------------------------------------------- 1 | FROM phusion/baseimage:0.10.0 2 | LABEL maintainer "Nazar Mokrynskyi " 3 | 4 | COPY webserver-common /webserver-common/ 5 | 6 | RUN \ 7 | 8 | /webserver-common/create-git-user.sh && \ 9 | 10 | /webserver-common/apt-get-update.sh && \ 11 | /webserver-common/apt-get-install-common.sh && \ 12 | /webserver-common/apt-get-install-ceph-fuse.sh && \ 13 | 14 | apt-get install -y --no-install-recommends git mc wget \ 15 | php-cli \ 16 | php-curl \ 17 | php-bcmath \ 18 | php-bz2 \ 19 | php-exif \ 20 | php-ftp \ 21 | php-gd \ 22 | php-gettext \ 23 | php-mbstring \ 24 | php-mcrypt \ 25 | php-mysqli \ 26 | php-opcache \ 27 | php-shmop \ 28 | php-sockets \ 29 | php-sysvmsg \ 30 | php-sysvsem \ 31 | php-sysvshm \ 32 | php-zip && \ 33 | 34 | /webserver-common/apt-get-cleanup.sh && \ 35 | 36 | # Enable SSH 37 | 38 | rm -f /etc/service/sshd/down && \ 39 | 40 | # Rename SSH config directory 41 | 42 | mv /etc/ssh /etc/ssh_dist && \ 43 | 44 | # Install Composer 45 | 46 | curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 47 | 48 | COPY ssh/webserver-entrypoint.sh / 49 | 50 | ENV \ 51 | CEPH_MON_SERVICE=ceph-mon \ 52 | CEPHFS_MOUNT=0 \ 53 | 54 | TERM=xterm 55 | 56 | VOLUME \ 57 | /data \ 58 | /etc/ssh 59 | 60 | ENTRYPOINT ["/webserver-entrypoint.sh"] 61 | -------------------------------------------------------------------------------- /build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -f Dockerfile-backup -t nazarpc/webserver:backup . 4 | docker build -f Dockerfile-ceph -t nazarpc/webserver:ceph . 5 | docker build -f Dockerfile-consul -t nazarpc/webserver:consul . 6 | docker build -f Dockerfile-data -t nazarpc/webserver:data . 7 | docker build -f Dockerfile-haproxy -t nazarpc/webserver:haproxy . 8 | docker build -f Dockerfile-logrotate -t nazarpc/webserver:logrotate . 9 | docker build -f Dockerfile-mariadb -t nazarpc/webserver:mariadb . 10 | docker build -f Dockerfile-nginx -t nazarpc/webserver:nginx . 11 | docker build -f Dockerfile-php-fpm -t nazarpc/webserver:php-fpm . 12 | docker build -f Dockerfile-phpmyadmin -t nazarpc/webserver:phpmyadmin . 13 | docker build -f Dockerfile-restore -t nazarpc/webserver:restore . 14 | docker build -f Dockerfile-ssh -t nazarpc/webserver:ssh . 15 | -------------------------------------------------------------------------------- /ceph/webserver-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ "$CONSUL_SERVICE" ]; then 5 | export KV_IP=$CONSUL_SERVICE 6 | fi 7 | 8 | if [ ! "$MON_IP" ]; then 9 | if [ "$1" = 'mon' ]; then 10 | # If we are running monitor - use own IP 11 | export MON_IP=`/webserver-common/get-own-ip.sh` 12 | else 13 | # Otherwise use host name 14 | export MON_IP=$CEPH_MON_SERVICE 15 | fi 16 | fi 17 | 18 | if [ ! "$CEPH_PUBLIC_NETWORK" ]; then 19 | export CEPH_PUBLIC_NETWORK=`ip addr | grep $MON_IP | awk '{ print $2; exit }'` 20 | fi 21 | 22 | exec /entrypoint.sh $@ 23 | -------------------------------------------------------------------------------- /consul/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "client_addr" : "0.0.0.0", 3 | "data_dir" : "/var/lib/consul", 4 | "disable_update_check" : true, 5 | "leave_on_terminate" : true, 6 | "ports" : { 7 | "dns" : 53, 8 | "http" : 80 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /consul/webserver-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | own_ip=`/webserver-common/get-own-ip.sh` 4 | cmd="consul agent -server -advertise $own_ip -config-dir /etc/consul.d -bootstrap-expect $MIN_SERVERS $@" 5 | 6 | # Try to join other nodes in cluster 7 | for node_ip in `/webserver-common/list-service-nodes.sh $SERVICE_NAME`; do 8 | if [[ "$node_ip" && "$node_ip" != "$own_ip" ]]; then 9 | cmd="$cmd -retry-join $node_ip" 10 | fi 11 | done 12 | 13 | exec $cmd 14 | -------------------------------------------------------------------------------- /docs/advanced.md: -------------------------------------------------------------------------------- 1 | NOTE: Description below is experimental and might not work as expected or not work at all, DO NOT USE IN PRODUCTION, but report any issues you've encountered, please:) 2 | 3 | # Advanced usage 4 | Advanced usage includes ability to build cluster of multiple nodes that scale nicely and load balancing. 5 | 6 | Good news: all your usual services like are already prepared for this, just need to be configured slightly differently. 7 | 8 | For instance, MariaDB instance is not a regular MariaDB server, but rather MariaDB Galera cluster with just one node, so you can scale it to multiple nodes at any time. 9 | 10 | The same about PhpMyAdmin, Nginx, PHP-FPM and SSH images - they are all ready to work in cluster, so you don't have to change your code at all, just modify declarative `docker-compose.yml` file. 11 | 12 | TODO: backup/restore/upgrade details needs to be updated here 13 | Upgrade procedure remains the same as before, backup and restore depends on your persistent storage. 14 | 15 | # Setup that works and setup that doesn't 16 | Setup described below is what should ideally work, but doesn't work quite yet (see [docker/docker#31157](https://github.com/docker/docker/issues/31157) and linked issues for details). 17 | 18 | The main issue is persistent storage. This repository contains Ceph image that can be used as persistent storage by mounting CephFS into target images, but Docker doesn't currently allow to mount anything within containers deployed using Docker Swarm Mode, so we can create Ceph cluster, but can't use it when needed. 19 | 20 | In order to overcome this and use built-in clusterization support you'll need to do 2 things: 21 | * have existing persistent storage and mount volume `/data` (also `/etc/ssh` for SSH image) in target services 22 | * remove from `docker-compose.yml` following lines, since they are provided by previous step and not working yet: 23 | ```yml 24 | environment: 25 | CEPHFS_MOUNT: 1 26 | # In order to access FUSE 27 | devices: 28 | - /dev/fuse:/dev/fuse 29 | # In order to mount CEPHFS 30 | cap_add: 31 | - SYS_ADMIN 32 | ``` 33 | * ignore Ceph and Consul images altogether if you don't need them for other purposes 34 | 35 | # Zero-configuration 36 | All the features provided work with zero-configuration out of the box, all you need is declarative `docker-compose.yml` file. 37 | 38 | IP addresses, configuration files and other stuff will be done for you completely automatically with reasonable defaults (which you can, of course, change at any time). 39 | 40 | As you scale your services - new nodes will join existing cluster, removed nodes will leave cluster and failed instances will do their best to re-join existing cluster as soon as they start again. 41 | 42 | Everything mentioned above makes building scalable infrastructure a breathe! 43 | 44 | If you think some of the features are not configured as good as they can - feel free to open an issue or submit a pull request. 45 | 46 | # Ceph 47 | One of the first major issue that needs to be solved when creating cluster is storage. While Docker supports volumes and their drivers, they need a lot of manual work to configure them, would be much better to have something more portable. 48 | 49 | We solve storage problem with Ceph, more precisely, with [Ceph FS](http://docs.ceph.com/docs/master/cephfs/). 50 | 51 | In order for this to work, we create Ceph cluster and then mount necessary directories with [ceph-fuse](http://docs.ceph.com/docs/master/man/8/ceph-fuse/) so that multiple containers on different nodes of the cluster will have access to the same files. 52 | 53 | For this purpose we'll use `nazarpc/webserver:ceph-v1` image. It can work in different modes, which is why we specify different commands: 54 | ```yml 55 | # docker-compose.yml 56 | version: '3.1' 57 | services: 58 | ... 59 | ceph-mon: 60 | image: nazarpc/webserver:ceph-v1 61 | command: mon 62 | restart: always 63 | 64 | ceph-osd: 65 | image: nazarpc/webserver:ceph-v1 66 | command: osd 67 | restart: always 68 | 69 | ceph-mds: 70 | image: nazarpc/webserver:ceph-v1 71 | command: mds 72 | restart: always 73 | ``` 74 | Currently it is not possible to specify scaling in YAML file, so we'll likely need to scale ceph nodes like following: 75 | ```bash 76 | docker-compose scale ceph-mon=3 ceph-osd=3 ceph-mds=3 77 | ``` 78 | Main environment variables supported: 79 | * `CONSUL_SERVICE` - OPTIONAL (defaults to `consul`), name of Consul service in `docker-compose.yml` declaration, needed for storing configuration 80 | * `CEPH_MON_SERVICE` OPTIONAL (defaults to `ceph-mon`), name of Ceph monitor service in `docker-compose.yml` declaration, needed in order to find Ceph monitor nodes in cluster 81 | 82 | NOTE: Any features supported by upstream [ceph/daemon](https://github.com/ceph/ceph-docker) image are inherently supported by this image. 83 | 84 | # Consul 85 | Consul is an integral piece of the cluster, since it is actually required for Ceph to store configuration 86 | 87 | For this purpose we'll use `nazarpc/webserver:consul-v1` image. 88 | ```yml 89 | # docker-compose.yml 90 | version: '3.1' 91 | services: 92 | ... 93 | consul: 94 | image: nazarpc/webserver:consul-v1 95 | restart: always 96 | ``` 97 | Currently it is not possible to specify scaling in YAML file (see [docker/compose#1661](https://github.com/docker/compose/issues/1661) and [docker/compose#2496](https://github.com/docker/compose/issues/2496)), so we'll likely need to scale Consul nodes (to at least `MIN_SERVERS`) like following: 98 | ```bash 99 | docker-compose scale consul=3 100 | ``` 101 | Main environment variables supported: 102 | * `CONSUL_SERVICE` - OPTIONAL (defaults to `consul`), name of Consul service in `docker-compose.yml` declaration (name of service itself actually), needed to find other Consul nodes in cluster 103 | * `MIN_SERVERS` - OPTIONAL (defaults to `3`), Consul cluster will wait till `MIN_SERVERS` started before forming cluster and starting leader election 104 | 105 | # HAProxy 106 | Load Balancing is also an integral part of cluster robustness and performance. HAProxy might be used to hide behind multiple nodes under single entry point and distribute incoming requests across those nodes. 107 | HAProxy is only useful for TCP connections. 108 | 109 | For this purpose we'll use `nazarpc/webserver:haproxy-v1` image, for instance: 110 | ```yml 111 | # docker-compose.yml 112 | version: '3.1' 113 | services: 114 | ... 115 | mariadb-haproxy: 116 | image: nazarpc/webserver:haproxy-v1 117 | restart: always 118 | environment: 119 | SERVICE_NAME: mariadb 120 | SERVICE_PORTS: 3306 121 | ``` 122 | Main environment variables supported: 123 | * `SERVICE_NAME` - REQUIRED, name of service for load balancing, for multiple services create multiple HAProxy services, they're lightweight, so you can create as many of them as needed 124 | * `SERVICE_PORTS` - REQUIRED, coma and/or space-separated list of ports that HAProxy will listen, HAProxy works as transparent proxy, so input port is the same as output port 125 | 126 | In example above all requests to the host `mariadb-haproxy` and port `3306` will be sent over to one of running `mariadb` instances. 127 | You can also scale `mariadb-haproxy` instance if needed. 128 | 129 | # MariaDB 130 | In order to scale MariaDB we'll need to build MariaDB Galera cluster. 131 | `nazarpc/webserver:mariadb-v1` image is in fact already a cluster with single node - so, it it ready to scale at any time with Master-Master replication mode. 132 | 133 | ```yml 134 | # docker-compose.yml 135 | version: '3.1' 136 | services: 137 | ... 138 | mariadb: 139 | image: nazarpc/webserver:mariadb-v1 140 | restart: always 141 | environment: 142 | CEPHFS_MOUNT: 1 143 | # In order to access FUSE 144 | devices: 145 | - /dev/fuse:/dev/fuse 146 | # In order to mount CEPHFS 147 | cap_add: 148 | - SYS_ADMIN 149 | ``` 150 | Main environment variables supported: 151 | * `SERVICE_NAME` - OPTIONAL (defaults to `mariadb`), name of MariaDB service in `docker-compose.yml` declaration, needed in order to find other MariaDB nodes in cluster 152 | * `CEPH_MON_SERVICE` OPTIONAL (defaults to `ceph-mon`), name of Ceph monitor service in `docker-compose.yml` declaration, needed in order to find Ceph monitor nodes in cluster 153 | * `CEPHFS_MOUNT` OPTIONAL (defaults to `0`), when set to `1`, container will try to mount CephFS on start 154 | 155 | If you scale `mariadb` instance - new nodes will join existing cluster and start to replicate: 156 | ```bash 157 | docker-compose scale mariadb=3 158 | ``` 159 | 160 | # Nginx, PHP-FPM and SSH 161 | They are very similar to MariaDB in terms of configuration, but don't create clusters by themselves. 162 | 163 | All you need to specify is that CephFS should be mounted: 164 | ```yml 165 | # docker-compose.yml 166 | version: '3.1' 167 | services: 168 | ... 169 | nginx: 170 | image: nazarpc/webserver:nginx-v1 171 | restart: always 172 | environment: 173 | CEPHFS_MOUNT: 1 174 | # In order to access FUSE 175 | devices: 176 | - /dev/fuse:/dev/fuse 177 | # In order to mount CEPHFS 178 | cap_add: 179 | - SYS_ADMIN 180 | 181 | php: 182 | image: nazarpc/webserver:php-fpm-v1 183 | restart: always 184 | environment: 185 | CEPHFS_MOUNT: 1 186 | # In order to access FUSE 187 | devices: 188 | - /dev/fuse:/dev/fuse 189 | # In order to mount CEPHFS 190 | cap_add: 191 | - SYS_ADMIN 192 | 193 | ssh: 194 | image: nazarpc/webserver:ssh-v1 195 | restart: always 196 | environment: 197 | CEPHFS_MOUNT: 1 198 | # In order to access FUSE 199 | devices: 200 | - /dev/fuse:/dev/fuse 201 | # In order to mount CEPHFS 202 | cap_add: 203 | - SYS_ADMIN 204 | ``` 205 | Main environment variables supported (common to all 3 images): 206 | * `CEPH_MON_SERVICE` OPTIONAL (defaults to `ceph-mon`), name of Ceph monitor service in `docker-compose.yml` declaration, needed in order to find Ceph monitor nodes in cluster 207 | * `CEPHFS_MOUNT` OPTIONAL (defaults to `0`), when set to `1`, container will try to mount CephFS on start 208 | 209 | # Combined example 210 | ```yml 211 | # docker-compose.yml 212 | version: '3.1' 213 | services: 214 | consul: 215 | image: nazarpc/webserver:consul-v1 216 | restart: always 217 | 218 | ceph-mon: 219 | image: nazarpc/webserver:ceph-v1 220 | command: mon 221 | restart: always 222 | 223 | ceph-osd: 224 | image: nazarpc/webserver:ceph-v1 225 | command: osd 226 | restart: always 227 | 228 | ceph-mds: 229 | image: nazarpc/webserver:ceph-v1 230 | command: mds 231 | restart: always 232 | 233 | mariadb-haproxy: 234 | image: nazarpc/webserver:haproxy-v1 235 | restart: always 236 | environment: 237 | SERVICE_NAME: mariadb 238 | SERVICE_PORTS: 3306 239 | 240 | mariadb: 241 | image: nazarpc/webserver:mariadb-v1 242 | restart: always 243 | environment: 244 | CEPHFS_MOUNT: 1 245 | # In order to access FUSE 246 | devices: 247 | - /dev/fuse:/dev/fuse 248 | # In order to mount CEPHFS 249 | cap_add: 250 | - SYS_ADMIN 251 | 252 | nginx: 253 | image: nazarpc/webserver:nginx-v1 254 | restart: always 255 | environment: 256 | CEPHFS_MOUNT: 1 257 | # In order to access FUSE 258 | devices: 259 | - /dev/fuse:/dev/fuse 260 | # In order to mount CEPHFS 261 | cap_add: 262 | - SYS_ADMIN 263 | 264 | php: 265 | image: nazarpc/webserver:php-fpm-v1 266 | restart: always 267 | environment: 268 | CEPHFS_MOUNT: 1 269 | # In order to access FUSE 270 | devices: 271 | - /dev/fuse:/dev/fuse 272 | # In order to mount CEPHFS 273 | cap_add: 274 | - SYS_ADMIN 275 | 276 | phpmyadmin: 277 | image: nazarpc/webserver:phpmyadmin-v1 278 | restart: always 279 | environment: 280 | MYSQL_HOST: mariadb 281 | 282 | ssh: 283 | image: nazarpc/webserver:ssh-v1 284 | restart: always 285 | environment: 286 | CEPHFS_MOUNT: 1 287 | # In order to access FUSE 288 | devices: 289 | - /dev/fuse:/dev/fuse 290 | # In order to mount CEPHFS 291 | cap_add: 292 | - SYS_ADMIN 293 | ``` 294 | ```bash 295 | docker-compose up -d 296 | docker-compose scale consul=3 ceph-mon=3 ceph-osd=3 ceph-mds=3 mariadb-haproxy=2 mariadb=3 nginx=2 php=3 297 | ``` 298 | 299 | After this we'll have: 300 | * 3 Consul nodes in cluster 301 | * Ceph cluster with 3 monitors, 3 OSDs and 3 metadata servers 302 | * 2 HAProxy instances for MariaDB load balancing 303 | * 3 MariaDB Galera nodes in cluster with Master-Master replication mode 304 | * 2 Nginx instances 305 | * 3 PHP-FPM instances 306 | * 1 PhpMyAdmin instance to work with MariaDB Galera cluster in GUI mode 307 | * 1 SSH server to access files and configs 308 | 309 | # TODO: Extend example with Docker Swarm Mode 310 | -------------------------------------------------------------------------------- /haproxy/haproxy.conf: -------------------------------------------------------------------------------- 1 | defaults 2 | timeout connect 5s 3 | timeout client 1m 4 | timeout server 1m 5 | 6 | # Generated configuration below 7 | -------------------------------------------------------------------------------- /haproxy/webserver-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | config_file=/usr/local/etc/haproxy/haproxy.conf 4 | 5 | if [ ! "$SERVICE_NAME" ]; then 6 | echo 'SERVICE_NAME environmental variable not specified, aborted' 7 | exit 8 | fi 9 | 10 | if [ ! "$SERVICE_PORTS" ]; then 11 | echo 'SERVICE_PORTS environmental variable not specified, aborted' 12 | exit 13 | fi 14 | 15 | # Watch for DNS changes and keep configuration up to date 16 | while [ true ]; do 17 | cp $config_file.dist $config_file 18 | for service_port in `echo $SERVICE_PORTS | tr ',' ' '`; do 19 | echo -e "listen $SERVICE_NAME-$service_port\n\tmode tcp\n\tbind 0.0.0.0:$service_port" >> $config_file 20 | for service_ip in `/webserver-common/list-service-nodes.sh $SERVICE_NAME`; do 21 | echo -e "\tserver $SERVICE_NAME-$service_ip-$service_port $service_ip:$service_port" >> $config_file 22 | done 23 | done 24 | echo "Configuration updated, reloading" 25 | pid=`pidof haproxy` 26 | if [ "$pid" ]; then 27 | haproxy -f $config_file -sf $pid & 28 | else 29 | haproxy -f $config_file & 30 | fi 31 | /webserver-common/wait-for-dns-update.sh $SERVICE_NAME 32 | done 33 | -------------------------------------------------------------------------------- /hooks/post_push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | docker tag $IMAGE_NAME $IMAGE_NAME-v1 3 | docker push $IMAGE_NAME-v1 4 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017, Nazar Mokrynskyi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included 11 | in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 19 | OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /logrotate/logrotate.conf: -------------------------------------------------------------------------------- 1 | # Compress logs 2 | compress 3 | # Do not move files, just copy and then truncate to zero 4 | copytruncate 5 | # Add date to rotated log files 6 | dateext 7 | # One most recent log file will be left uncompressed 8 | delaycompress 9 | # We are OK if log files are missing 10 | missingok 11 | # Keep last 15 log files 12 | rotate 15 13 | # Rotate log file if it is bigger than 100 kilobytes 14 | size 100k 15 | # Rotate any logs 16 | /data/*/log/*.log {} 17 | -------------------------------------------------------------------------------- /mariadb/galera.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | # Enforcing mandatory settings 3 | wsrep_provider=/usr/lib/galera/libgalera_smm.so 4 | binlog_format=ROW 5 | bind-address=0.0.0.0 6 | default_storage_engine=InnoDB 7 | innodb_autoinc_lock_mode=2 8 | innodb_doublewrite=1 9 | innodb_flush_log_at_trx_commit=0 10 | transaction_isolation=READ-COMMITTED 11 | wsrep_on=ON 12 | -------------------------------------------------------------------------------- /mariadb/webserver-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /webserver-common/ceph-mount.sh /data 4 | 5 | # Automatic upgrade from older images 6 | # TODO: remove in future 7 | if [ -L /data/mysql/config ]; then 8 | rm /data/mysql/config /data/mysql/data /data/mysql/log 9 | mkdir /data/mysql/config /data/mysql/data /data/mysql/log 10 | mv /etc/mysql/* /data/mysql/config/ 11 | mv /var/lib/mysql/* /data/mysql/data/ 12 | mv /var/log/mysql/* /data/mysql/log/ 13 | sed -i 's/\/etc\/mysql/\/data\/mysql\/config/g' /data/mysql/config/my.cnf 14 | sed -i 's/\/var\/lib\/mysql_local/\/data\/mysql\/data/g' /data/mysql/config/my.cnf 15 | sed -i 's/\/var\/log\/mysql/\/data\/mysql\/log/g' /etc/mysql/my.cnf 16 | fi 17 | 18 | if [ ! -e /data/mysql ]; then 19 | mkdir -p /data/mysql/config /data/mysql/data /data/mysql/log 20 | cp -a /etc/mysql_dist/* /data/mysql/config/ 21 | fi 22 | 23 | chown git:git /data /data/mysql 24 | chown -R git:git /data/mysql/config 25 | chown mysql:mysql /data/mysql/data /data/mysql/log 26 | chmod 770 /data/mysql/data 27 | 28 | if [ ! -e /data/mysql/root_password ]; then 29 | pwgen -s 30 1 > /data/mysql/root_password 30 | fi 31 | 32 | export MYSQL_ROOT_PASSWORD=`cat /data/mysql/root_password` 33 | echo "MySQL root password (from /data/mysql/root_password): $MYSQL_ROOT_PASSWORD" 34 | 35 | if [ "$1" = 'mysqld' ]; then 36 | shift 37 | fi 38 | set -- --wsrep_sst_method=xtrabackup-v2 --wsrep_sst_auth=root:$MYSQL_ROOT_PASSWORD $@ 39 | 40 | # If this is not the master node of the service (first instance that have started) - do not use /data/mysql/data and /data/mysql/log and try to connect to the master node 41 | /webserver-common/determine-service-master-node.sh /data/mysql/master_node_ip $SERVICE_NAME 42 | master_node=`cat /data/mysql/master_node_ip` 43 | if [[ "$master_node" && (! "`cat /etc/hosts | grep $master_node`") ]]; then 44 | echo "Starting as regular node (no synchronization to permanent storage)" 45 | set -- --defaults-file=/tmp/mysql/config/my.cnf $@ 46 | 47 | if [ ! -e /tmp/mysql ]; then 48 | mkdir -p /tmp/mysql/config /tmp/mysql/data /tmp/mysql/log 49 | chown mysql:mysql /tmp/mysql/data /tmp/mysql/log 50 | cp /data/mysql/config/my.cnf /tmp/mysql/config/my.cnf 51 | sed -i 's/\/data\/mysql\/data/\/tmp\/mysql\/data/g' /tmp/mysql/config/my.cnf 52 | sed -i 's/\/data\/mysql\/log/\/tmp\/mysql\/log/g' /tmp/mysql/config/my.cnf 53 | # Initialize MariaDB using entrypoint from original image without last line 54 | gosu mysql /docker-entrypoint-init.sh "$@ --bind_address=127.0.0.1 --wsrep_cluster_address=gcomm:// --wsrep_on=OFF" 55 | fi 56 | if ! mysqladmin --host=$master_node --user=root --password=$MYSQL_ROOT_PASSWORD ping; then 57 | echo 'Master node is not ready, exiting' 58 | sleep 1 59 | fi 60 | set -- $@ --wsrep_cluster_address=gcomm://$master_node 61 | else 62 | echo "Starting as master node (with synchronization to permanent storage)" 63 | hostname > /data/mysql/master_node_hostname 64 | set -- --defaults-file=/data/mysql/config/my.cnf $@ 65 | 66 | # Initialize MariaDB using entrypoint from original image without last line 67 | gosu mysql /docker-entrypoint-init.sh "$@ --bind_address=127.0.0.1 --wsrep_cluster_address=gcomm:// --wsrep_on=OFF" 68 | 69 | # Find other existing node to connect to 70 | existing_nodes='' 71 | for node_ip in `/webserver-common/list-service-nodes.sh $SERVICE_NAME`; do 72 | if [[ "$node_ip" && "$node_ip" != "$master_node" ]]; then 73 | # Check if node is ready 74 | if mysqladmin --host=$node_ip --user=root --password=$MYSQL_ROOT_PASSWORD ping; then 75 | existing_nodes="$existing_nodes,$node_ip" 76 | break 77 | fi 78 | fi 79 | done 80 | # When first node fails to connect to any other node from the cluster, then we need to force its start 81 | # TODO: Ideally instances should communicate about who's version of history is more recent and then start cluster from that node, 82 | # but for now we assume master not to be always up to date 83 | if [ ! "$existing_nodes" ]; then 84 | echo "No existing alive nodes found in cluster, forcing start in master node" 85 | if [ -e /data/mysql/data/grastate.dat ]; then 86 | sed -i 's/safe_to_bootstrap: 0/safe_to_bootstrap: 1/g' /data/mysql/data/grastate.dat 87 | fi 88 | fi 89 | set -- $@ --wsrep_cluster_address=gcomm://${existing_nodes:1} 90 | fi 91 | 92 | if [ -e /data/mysql/before_start.sh ]; then 93 | bash /data/mysql/before_start.sh 94 | else 95 | touch /data/mysql/before_start.sh 96 | fi 97 | 98 | exec gosu mysql mysqld "$@" 99 | -------------------------------------------------------------------------------- /nginx/webserver-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | /webserver-common/ceph-mount.sh /data 5 | 6 | # Automatic upgrade from older images 7 | # TODO: remove in future 8 | if [ -L /data/nginx/config ]; then 9 | rm /data/nginx/config /data/nginx/log /data/nginx/www 10 | mkdir /data/nginx/config /data/nginx/log /data/nginx/www 11 | mv /etc/nginx/* /data/nginx/config/ 12 | mv /etc/nginx/.* /data/nginx/config/ 13 | mv /usr/share/nginx/html/* /data/nginx/www/ 14 | mv /usr/share/nginx/html/.* /data/nginx/www/ 15 | mv /var/log/nginx/* /data/nginx/log/ 16 | sed -i 's/\/etc\/nginx/\/data\/nginx\/config/g' `find /data/nginx/config -type f` 17 | sed -i 's/\/var\/log\/nginx/\/data\/nginx\/log/g' `find /data/nginx/config -type f` 18 | sed -i 's/\/usr\/share\/nginx\/html/\/data\/nginx\/www/g' `find /data/nginx/config -type f` 19 | echo -e "daemon off;\n$(cat /data/nginx/config/nginx.conf)" > /data/nginx/config/nginx.conf 20 | fi 21 | 22 | if [ ! -e /data/nginx ]; then 23 | mkdir -p /data/nginx/config /data/nginx/log /data/nginx/www 24 | cp -a /etc/nginx_dist/* /data/nginx/config/ 25 | if [ -z "$(ls -A /data/nginx/www)" ]; then 26 | cat <<-HTML > /data/nginx/www/index.html 27 | 28 | Hello, world!
29 | Docker webserver is alive and ready to serve requests:) 30 | HTML 31 | fi 32 | fi 33 | 34 | chown git:git /data /data/nginx /data/nginx/log /data/nginx/www 35 | chown -R git:git /data/nginx/config 36 | 37 | if [ "$1" = 'nginx' ]; then 38 | shift 39 | fi 40 | 41 | # With CephFS we can't put all all logs into single directory, so let's move them all into local temporary directory 42 | if [ $CEPHFS_MOUNT -eq 1 ]; then 43 | mkdir -p /tmp/nginx/config /tmp/nginx/log 44 | chown git:git /tmp/nginx/log 45 | cp -a /data/nginx/config/* /tmp/nginx/config/ 46 | sed -i 's/\/data\/nginx\/log/\/tmp\/nginx\/log/g' `find /tmp/nginx/config -type f` 47 | set -- -c /tmp/nginx/config/nginx.conf -p /tmp/nginx/config/ $@ 48 | else 49 | set -- -c /data/nginx/config/nginx.conf -p /data/nginx/config/ $@ 50 | fi 51 | 52 | if [ -e /data/nginx/before_start.sh ]; then 53 | bash /data/nginx/before_start.sh 54 | else 55 | touch /data/nginx/before_start.sh 56 | fi 57 | 58 | exec nginx "$@" 59 | -------------------------------------------------------------------------------- /php-fpm/webserver-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | /webserver-common/ceph-mount.sh /data 5 | 6 | # Automatic upgrade from older images 7 | # TODO: remove in future 8 | if [ -L /data/php/config ]; then 9 | rm /data/php/config 10 | mkdir /data/php/config 11 | mv /usr/local/etc/* /data/php/config 12 | sed -i 's/etc\/php-fpm.d/php-fpm.d/g' /data/php/config/php-fpm.conf 13 | fi 14 | 15 | if [ ! -e /data/php ]; then 16 | mkdir -p /data/php/config 17 | cp -a /usr/local/etc_dist/* /data/php/config/ 18 | fi 19 | 20 | chown git:git /data /data/php 21 | chown -R git:git /data/php/config 22 | 23 | if [ "$1" = 'php-fpm' ]; then 24 | shift 25 | fi 26 | 27 | set -- -c /data/php/config --prefix /data/php/config --fpm-config /data/php/config/php-fpm.conf 28 | 29 | if [ -e /data/php/before_start.sh ]; then 30 | bash /data/php/before_start.sh 31 | else 32 | touch /data/php/before_start.sh 33 | fi 34 | 35 | exec php-fpm "$@" 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # WebServer (MariaDB, PHP-FPM, Nginx) composed from several separate containers linked together 2 | Currently WebServer consists of such images: 3 | * Data-only container (based on official `debian:jessie` image) 4 | * Logrotate container (based on official `debian:jessie` image) 5 | * MariaDB (based on official `MariaDB` image) 6 | * Nginx (based on official `Nginx` image) 7 | * PHP-FPM (based on `nazarpc/php:fpm` image, which is official image + bunch of frequently used PHP extensions) 8 | * SSH (based on `phusion/baseimage` image, contains pre-installed `curl`, `git`, `mc`, `wget`, `php-cli` and `composer` for your convenience) 9 | * PhpMyAdmin (based on `nazarpc/phpmyadmin` image, which is official php image with Apache2, where PhpMyAdmin was installed) 10 | * Ceph (based on upstream `ceph/daemon` image) 11 | * Consul (based on official `debian:jessie` image) 12 | * HAProxy (based on official `haproxy` image) 13 | * Backup container (based on official `debian:jessie` image) 14 | * Restore container (based on official `debian:jessie` image) 15 | * [nazarpc/webserver-apps](https://github.com/nazar-pc/docker-webserver-apps) for ready to use applications that plays nicely with images mentioned above 16 | 17 | If you find this project useful, consider supporting its development on [patreon.com/nazarpc](https://www.patreon.com/nazarpc), this would help me a lot! 18 | 19 | # How to use 20 | The most convenient way to use all this is [Docker Compose](https://docs.docker.com/compose/) 21 | 22 | At first you'll need to create persistent data-only container that will store all files, databases, ssh keys and settings of all these things: 23 | ``` 24 | docker run --name example.com nazarpc/webserver:data-v1 25 | ``` 26 | 27 | NOTE: `-v1` suffix here and in all other places is optional (there are also images without `-v1` suffix and they are exactly the same), however, it is possible that in future images without suffixes become completely incompatible and `-v2` suffixed images will be introduced, so you'd better be protected from upgrading to incompatible image rather than getting broken setup at some point in future (this might not happen ever, but still). 28 | 29 | This container will start and stop immediately, that is OK. 30 | 31 | After this create directory for your website, it will contain `docker-compose.yml` file and potentially more files you'll need: 32 | ``` 33 | mkdir example.com 34 | cd example.com 35 | ``` 36 | 37 | Now create `docker-compose.yml` inside with following contents: 38 | 39 | ```yml 40 | version: '2' 41 | services: 42 | data: 43 | image: nazarpc/webserver:data-v1 44 | volumes_from: 45 | - container:example.com 46 | 47 | logrotate: 48 | image: nazarpc/webserver:logrotate-v1 49 | restart: always 50 | volumes_from: 51 | - data 52 | 53 | mariadb: 54 | image: nazarpc/webserver:mariadb-v1 55 | restart: always 56 | volumes_from: 57 | - data 58 | 59 | nginx: 60 | image: nazarpc/webserver:nginx-v1 61 | links: 62 | - php 63 | # ports: 64 | # - {ip where to bind}:{port on previous ip where to bind}:80 65 | restart: always 66 | volumes_from: 67 | - data 68 | 69 | php: 70 | image: nazarpc/webserver:php-fpm-v1 71 | links: 72 | - mariadb:mysql 73 | restart: always 74 | volumes_from: 75 | - data 76 | 77 | # phpmyadmin: 78 | # image: nazarpc/webserver:phpmyadmin-v1 79 | # links: 80 | # - mariadb:mysql 81 | # restart: always 82 | # ports: 83 | # - {ip where to bind}:{port on previous ip where to bind}:80 84 | 85 | ssh: 86 | image: nazarpc/webserver:ssh-v1 87 | restart: always 88 | volumes_from: 89 | - data 90 | # ports: 91 | # - {ip where to bind}:{port on previous ip where to bind}:22 92 | # environment: 93 | # PUBLIC_KEY: '{your public SSH key}' 94 | ``` 95 | 96 | Now customize it as you like, feel free to comment-out or remove `mariadb`, `php` or `ssh` container if you have just bunch of static files, also you can uncomment `phpmyadmin` container if needed. 97 | 98 | When you're done with editing: 99 | ``` 100 | docker-compose up -d 101 | ``` 102 | 103 | That is it, you have whole WebServer up and running! 104 | 105 | **Also you might be interested in [advanced examples](docs/advanced.md) with load balancing and scaling across cluster.** 106 | 107 | # Upgrade 108 | You can easily upgrade your WebServer to new version of software. 109 | 110 | Using Docker Compose upgrade is very simple: 111 | ``` 112 | docker-compose pull 113 | docker-compose up -d 114 | ``` 115 | All containers will be recreated from new images in few seconds. 116 | 117 | Backup/restore images are not present in `docker-compose.yml`, so if you're using them - pull them manually. 118 | 119 | Alternatively you can pull all images manually: 120 | ``` 121 | docker pull nazarpc/webserver:data-v1 122 | docker pull nazarpc/webserver:logrotate-v1 123 | docker pull nazarpc/webserver:mariadb-v1 124 | docker pull nazarpc/webserver:nginx-v1 125 | docker pull nazarpc/webserver:php-fpm-v1 126 | docker pull nazarpc/webserver:ssh-v1 127 | docker pull nazarpc/webserver:backup-v1 128 | docker pull nazarpc/webserver:restore-v1 129 | ``` 130 | 131 | And again in directory with `docker-compose.yml`: 132 | ``` 133 | docker-compose up -d 134 | ``` 135 | 136 | # Backup 137 | To make backup you need to only backup volumes of data-only container. The easiest way to do that is using `nazarpc/webserver:backup-v1` image: 138 | ``` 139 | docker run --rm --volumes-from=example.com -v /backup-on-host:/backup --env BACKUP_FILENAME=new-backup nazarpc/webserver:backup-v1 140 | ``` 141 | 142 | This will result in `/backup-on-host/new-backup.tar` file being created - feel free to specify other directory and other name for backup file. 143 | 144 | All other containers are standard and doesn't contain anything important, that is why upgrade process is so simple. 145 | 146 | **NOTE: You'll likely want to stop MariaDB instance before backup (it is enough to stop master node in case of MariaDB cluster with 2+ nodes)** 147 | 148 | # Restore 149 | Restoration from backup is not more difficult that making backup, there is `nazarpc/webserver:restore-v1` image for that: 150 | ``` 151 | docker run --rm --volumes-from=example.com -v /backup-on-host/new-backup.tar:/backup.tar nazarpc/webserver:restore-v1 152 | ``` 153 | 154 | That is it, empty just created `example.com` container will be filled with data from backup and ready to use. 155 | 156 | # SSH 157 | SSH might be needed to access files from outside, especially with git. 158 | 159 | Before you enter ssh container via SSH for the first time, you need to specify public SSH key ([how to generate SSH keys](https://help.github.com/articles/generating-ssh-keys/#step-2-generate-a-new-ssh-key)). 160 | The easiest way to do this is to define `PUBLIC_KEY` environment variable in `docker-compose.yml`. 161 | Alternatively you can create file `/data/.ssh/authorized_keys` and put your public key contents inside. 162 | For example, you can do that from Midnight Commander file manager: 163 | ``` 164 | docker-compose run ssh mc 165 | ``` 166 | 167 | When public SSH key is added you should be able to access container as `git` user: 168 | ``` 169 | ssh git@example.com 170 | ``` 171 | 172 | # Internal structure 173 | Internally all that matters is `/data` directory - it contains all necessary files (or symlinks sometimes) for your convenience - here you can see files for Nginx and MariaDB, their logs and configs, PHP-FPM's config, SSH config and SSH keys directory. 174 | That is all what will be persistent, everything else outside `/data` will be lost during upgrade. 175 | 176 | # Update configuration 177 | If you update some configuration - you don't need to restart everything, restart only specific service you need, for instance: 178 | ``` 179 | docker-compose restart nginx 180 | ``` 181 | 182 | # License 183 | MIT license, see license.txt 184 | -------------------------------------------------------------------------------- /ssh/webserver-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | /webserver-common/ceph-mount.sh \ 5 | /data \ 6 | /etc/ssh 7 | 8 | if [ ! -e /data/.ssh ]; then 9 | mkdir -p /data/.ssh 10 | fi 11 | 12 | if [[ "$PUBLIC_KEY" && (! -e /data/.ssh/authorized_keys || `cat /data/.ssh/authorized_keys | grep -F "$PUBLIC_KEY"` = '') ]]; then 13 | echo $PUBLIC_KEY >> /data/.ssh/authorized_keys 14 | fi 15 | 16 | if [ ! -e /data/ssh ]; then 17 | mkdir -p /data/ssh 18 | ln -s /etc/ssh /data/ssh/config 19 | fi 20 | 21 | chown git:git /data 22 | 23 | if [ ! -e /etc/ssh/sshd_config ]; then 24 | cp -a /etc/ssh_dist/* /etc/ssh/ 25 | fi 26 | 27 | if [ -e /data/ssh/before_start.sh ]; then 28 | bash /data/ssh/before_start.sh 29 | fi 30 | 31 | exec /sbin/my_init 32 | -------------------------------------------------------------------------------- /webserver-common/apt-get-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | apt-get clean 4 | rm -rf /var/lib/apt/lists/* 5 | -------------------------------------------------------------------------------- /webserver-common/apt-get-install-ceph-fuse.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ceph repository for up to date version of ceph-fuse package; Ceph-fuse itself is used as cluster filesystem CephFS 4 | 5 | # TODO: Do not install Ceph until http://tracker.ceph.com/issues/21585 is resolved 6 | #CEPH_VERSION=kraken 7 | #curl -sSL 'https://download.ceph.com/keys/release.asc' | apt-key add - 8 | #echo "deb http://download.ceph.com/debian-$CEPH_VERSION/ $(cat /etc/apt/sources.list | awk '{ print $3; exit }') main" > /etc/apt/sources.list.d/ceph-$CEPH_VERSION.list 9 | #apt-get update 10 | #apt-get install -y --no-install-recommends ceph-fuse 11 | -------------------------------------------------------------------------------- /webserver-common/apt-get-install-common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # We'll need dnsutils package for DNS lookups, curl is used in many images for downloading stuff 4 | 5 | apt-get install -y --no-install-recommends curl ca-certificates dnsutils 6 | -------------------------------------------------------------------------------- /webserver-common/apt-get-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | apt-get update 4 | apt-get upgrade -y 5 | -------------------------------------------------------------------------------- /webserver-common/ceph-mount.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ ! "$@" || $CEPHFS_MOUNT -ne 1 ]]; then 4 | exit 5 | fi 6 | 7 | echo "Have some mount points to mount" 8 | 9 | mkdir -p /ceph 10 | mkdir -p /etc/ceph 11 | echo -e "[global]\nauth client required = none" > /etc/ceph/ceph.conf 12 | echo "Mounting /ceph..." 13 | while ! `ceph-fuse -m $CEPH_MON_SERVICE /ceph && mount | grep -q /ceph`; do 14 | echo "Mounting failed, trying again in 1 second" 15 | sleep 1 16 | done 17 | 18 | for mount_point in $@; do 19 | echo "Mounting $mount_point..." 20 | mkdir -p /ceph$mount_point 21 | mount --bind /ceph$mount_point $mount_point 22 | done 23 | -------------------------------------------------------------------------------- /webserver-common/create-git-user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | addgroup -gid 1000 git 4 | useradd -d /data -s /bin/bash -g 1000 -u 1000 git 5 | passwd -d git 6 | -------------------------------------------------------------------------------- /webserver-common/determine-service-master-node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # First argument is file where master node IP will be stored 4 | master_node_ip_file=$1 5 | # Second argument is the service name 6 | service_nodes=`/webserver-common/list-service-nodes.sh $2` 7 | first_node=`echo $service_nodes | awk '{ print $1; exit }'` 8 | if [ ! -e $master_node_ip_file ]; then 9 | echo "No master node found, changing to the first node $first_node" 10 | master_node=$first_node 11 | echo $master_node > $master_node_ip_file 12 | else 13 | master_node=`cat $master_node_ip_file` 14 | if [ ! "`echo $service_nodes | grep $master_node`" ]; then 15 | echo "Master node $master_node not reachable, changing to the first node $first_node" 16 | master_node=$first_node 17 | echo $master_node > $master_node_ip_file 18 | fi 19 | fi 20 | echo "Master node IP: $master_node" 21 | -------------------------------------------------------------------------------- /webserver-common/get-own-ip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat /etc/hosts | grep `hostname` | awk '{ print $1; exit }' 4 | -------------------------------------------------------------------------------- /webserver-common/list-service-nodes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dig $1 a +short | sort 4 | -------------------------------------------------------------------------------- /webserver-common/readme.md: -------------------------------------------------------------------------------- 1 | This directory contains some scripts that are common for multiple images in order to avoid files duplication and ease maintaining 2 | -------------------------------------------------------------------------------- /webserver-common/wait-for-dns-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | service_nodes_old=`/webserver-common/list-service-nodes.sh $1` 4 | while [ true ]; do 5 | sleep 1 6 | service_nodes_new=`/webserver-common/list-service-nodes.sh $1` 7 | if [ "$service_nodes_new" != "$service_nodes_old" ]; then 8 | exit 0 9 | fi 10 | done 11 | --------------------------------------------------------------------------------