Docker Development Server

Install Alpine Linux

  • download a standard or an extended ISO image
  • boot the ISO image by IPMI SuperMicro menu “Remote Control/Console Redirection” or “Virtual Media/CD-ROM Image”
  • login as the root user with an empty password
  • in the case of Alpine Standard (not Extended) ISO image
# set up network interfaces and start networking
setup-interfaces && /etc/init.d/networking start
# set up APK repository and update its cache
setup-apkrepos -r && apk update
  • run setup-alpine -f answer-file.sh with the following answer file:
# Use US layout with US variant
KEYMAPOPTS="us us"
# Ask for hostname
HOSTNAMEOPTS=
# Ask for interfaces (autodetect)
INTERFACESOPTS=
# Ask for DNS domain name and nameservers (autodetect)
DNSOPTS=
# Set timezone to UTC
TIMEZONEOPTS="-z Europe/Prague"
# Disable a http/ftp proxy
PROXYOPTS=none
# Detect and add fastest mirror
APKREPOSOPTS="-f"
# Install OpenSSH
SSHDOPTS="-c openssh"
# Use busybox NTP daemon
NTPOPTS="-c busybox"
# Ask for a disk where to permanently install Alpine; create root / btrfs-partition and non-UEFI /boot ext4-partition
DISKOPTS="-m sys -v"
export BOOTFS="ext4"
export ROOTFS="btrfs"

Configure Alpine Linux

# Add a user
adduser -g "${USERGIVENNAME} ${USERSURNAME}" -G users ${USERNAME}
# Enable the root filesystem compression for btrfs (it seems that fstab is ignored for /, so we remount it by init.d)
sed -i 's|\(/\s\+btrfs\s\+[^ \t]\+\)|\1,compress=zlib|' /etc/fstab
cat >/etc/local.d/root-remount-compress.start <<END
#!/bin/sh
exec mount / -o remount,compress=zlib
END
chmod 755 /etc/local.d/root-remount-compress.start
rc-service local start && rc-update add local default
# Remove a welcome message banner
rm /etc/motd
# Enable additional package repositories and updgrade the system
sed -i -e 's|^#\(.*/v[0-9.]\+/community\)$|\1|g' -e 's|^#\(.*/edge/main\)$|@edge \1|g' -e 's|^#\(.*/edge/community\)$|@community \1|g' -e 's|^#\(.*/edge/testing\)$|@testing \1|g' /etc/apk/repositories
apk update && apk upgrade --latest
# Install bash-completion
apk add bash-completion
# Set ssmtp MTA forwarder
setup-mta
# Install fail2ban for SSHd protection
apk add fail2ban && rc-service fail2ban start && rc-update add fail2ban default
# Install logrotate for /var/log management
apk add logrotate && rc-service crond restart
# Install an IPMI support
apk add ipmitool && rc-service ipmievd start && rc-update add ipmievd default
# Install lm_sensors and dmidecode for HW sensors/IPMI monitoring and fancontrol
apk add lm_sensors lm_sensors-sensord dmidecode
apk add --virtual lm_sensors-detect-deps lm_sensors-detect
modprobe i2c-dev && echo i2c-dev >> /etc/modules
modprobe eeprom && echo eeprom >> /etc/modules
sensors-detect
apk del lm_sensors-detect-deps
rc-service sensord start && rc-update add sensord default
pwmconfig
rc-service fancontrol start && rc-update add fancontrol default
# Install smartmontools for HDD monitoring with reporting by an email
apk add smartmontools
rc-service smartd start && rc-update add smartd default
# Install Docker and docker-compose
apk add docker docker-bash-completion && rc-service docker start && rc-update add docker boot
apk add py-pip && pip install docker-compose
# Install git
apk add git git-bash-completion
# Add the user to new groups
for I in audio cdrom floppy games usb video wheel docker; do adduser ${USERNAME} ${I}; done

Configure Firewall

apk add ufw@testing
ufw default deny incoming
ufw default allow outgoing
ufw allow in ssh
ufw limit ssh
ufw allow in http
ufw allow in https
ufw allow in 1521
ufw allow in 2222
ufw limit 2222
ufw allow in 2223
ufw limit 2223
echo y | ufw enable
ufw status verbose

Configure Docker Compose

  • create a docker-compose service /etc/init.d/docker-compose
#!/sbin/openrc-run
# Copyright 1999-2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

description="Docker Compose"
description_reload="Reload services"

extra_started_commands="reload"

: ${DOCKERCOMPOSE_PROJECT_NAME:=boot}
: ${DOCKERCOMPOSE_YAMLFILE:=/etc/docker-compose/docker-compose.yml}
: ${DOCKERCOMPOSE_OPTS:=-f $DOCKERCOMPOSE_YAMLFILE -p $DOCKERCOMPOSE_PROJECT_NAME}
: ${DOCKERCOMPOSE_UPOPTS:=-d --no-recreate --no-build --no-deps}

depend() {
    use docker
}

start() {
    ebegin "Starting ${SVCNAME}"
    docker-compose $DOCKERCOMPOSE_OPTS up $DOCKERCOMPOSE_UPOPTS
    eend $?
}

stop() {
    ebegin "Stopping ${SVCNAME}"
    docker-compose $DOCKERCOMPOSE_OPTS stop
    eend $?
}

reload() {
    ebegin "Reloading ${SVCNAME}"
    docker-compose $DOCKERCOMPOSE_OPTS up $DOCKERCOMPOSE_UPOPTS
    eend $?
}
  • create its environment file /etc/conf.d/docker-compose
# /etc/conf.d/docker-compose: config file for /etc/init.d/docker-compose

# an alternate project name
#DOCKERCOMPOSE_PROJECT_NAME=boot

# an alternate compose file
#DOCKERCOMPOSE_YAMLFILE=/etc/docker-compose/docker-compose.yml

# how to create and start containers
#DOCKERCOMPOSE_UPOPTS=-d --no-recreate --no-build --no-deps
  • run the service
chmod 755 /etc/init.d/docker-compose
rc-service docker-compose start && rc-update add docker-compose default

Install Git Annex

  • crate a script /usr/local/bin/git-annex.linux-update.sh to install git-annex static binaries
#!/bin/sh

DIR=/opt/git-annex.linux
DOWNLOAD=/tmp/git-annex-standalone-amd64.tar.gz

wget "https://downloads.kitenet.net/git-annex/linux/current/git-annex-standalone-amd64.tar.gz" -O ${DOWNLOAD} \
&& rm -rf ${DIR} \
&& tar xzf ${DOWNLOAD} -C ${DIR%/*} \
&& rm ${DOWNLOAD}

ln -vs ${DIR}/git-annex ${DIR}/git-annex-shell /usr/bin

HTTP/HTTPs Reverse Proxy/Load Balancer to Docker Containers

See Træfik HTTP reverse proxy and load balancer.

  • prepare a configuration directory
# create a configuration directory whose files will be mounted into the container as volumes of ocnfiguration files
mkdir -p /etc/traefik
touch /etc/traefik/acme.json
chmod 600 /etc/traefik/acme.json
  • create a /etc/traefik/traefik.toml configuration file
defaultEntryPoints = ["https"]

[entryPoints]
  [entryPoints.http]
  address = ":80"
  compress = true
#    [entryPoints.http.redirect]
#    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
    [entryPoints.https.tls]

[acme]
entryPoint = "https"
email = "myemail@myhostname.mydomain"
acmelogging = true
onhostrule = true
storage = "acme.json"
  [acme.httpChallenge]
  entryPoint = "http"

[[acme.domains]]
main = "myhostname.mydomain"
  • create a docker-compose.yml
version: '3'
services:
  revproxy:
    image: traefik
#    image: containous/traefik:latest
    command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /etc/traefik/traefik.toml:/traefik.toml
      - /etc/traefik/acme.json:/acme.json
    ports:
      - "80:80"
      - "443:443"
    labels:
      - "traefik.backend=revproxy"
      - "traefik.port=8080"
      - "traefik.frontend.rule=PathPrefixStrip:/traefik/"

RoundCube WebMail Docker Container

See Roundcube Webmail Docker image and configure it with the HTTPs reverse proxy.

  • prepare a data-store directory
# create a local directory that will be mounted into the container as a volume to store configuration and data (copy the directory content from the git repo above)
mkdir -p /home/roundcubemail
# allow to use the directory by all users (roundcubemail container will use UID:GID nobody:nobody)
chmod 1777 /home/roundcubemail
  • create a docker-compose.yml
version: '3'
services:
  roundcubemail:
    image: registry.gitlab.com/rychly-docker/docker-roundcubemail
    volumes:
      - /home/roundcubemail:/home/roundcubemail
    labels:
      - "traefik.backend=roundcubemail"
      - "traefik.port=80"
      - "traefik.frontend.rule=PathPrefixStrip:/roundcubemail/"

GitLab and GitLab Runner Docker Conatiners

  • prepare configuration and data storage directories
mkdir -p /etc/gitlab /etc/docker-compose/gitlab /home/gitlab /home/gitlab-runner
  • add new entrypoint script /etc/docker-compose/gitlab/docker-entrypoint.sh
#!/bin/bash
#
# Fix ISSUE: Docker image : can't load the gem 'sys-filesystem'.
# https://gitlab.com/gitlab-org/omnibus-gitlab/issues/1809
#

set -e

if [[ ! -e /usr/bin/setfattr ]]; then
    apt-get update
    apt-get install -y attr
fi

find / -type f -executable -name ruby -exec setfattr -n user.pax.flags -v em {} \; -print

exec ${@:-/assets/wrapper}
  • allow execution of the entrypoint script
chmod 755 /etc/docker-compose/gitlab/*.sh
  • create a new configuration file for GitLab-Runner in /home/gitlab-runner/config.toml
concurrent = 1
check_interval = 0
  • create a docker-compose.yml
version: '3'
services:
  gitlab-runner:
    image: gitlab/gitlab-runner:alpine
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /home/gitlab-runner:/etc/gitlab-runner
    labels:
      - "traefik.enable=false"
  gitlab:
    image: gitlab/gitlab-ce:latest
    restart: always
    entrypoint: /custom-entrypoint/docker-entrypoint.sh
#    environment:
#      - GITLAB_HOST=https://myhostname.mydomain/gitlab
#      # listen_* and proxy_* to disable HTTPs that will be autodetected from the external_url
#      # real_ip_* to keep users from being listed as signed in from GitLab's trusted reverse proxies IPs
#      - GITLAB_OMNIBUS_CONFIG="external_url 'https://myhostname.mydomain/gitlab'; gitlab_rails['gitlab_shell_ssh_port'] = 2222; nginx['listen_port'] = 80; nginx['listen_https'] = false; nginx['proxy_set_headers'] = { 'X-Forwarded-Proto' => 'https', 'X-Forwarded-Ssl' => 'on' }; nginx['real_ip_trusted_addresses'] = [ '172.16.0.0/12' ]; nginx['real_ip_header'] = 'X-Real-IP'; nginx['real_ip_recursive'] = 'on';"
#      # the rest of docker-independent configuration is in /etc/gitlab/gitlab.rb
    ports:
      - "2222:22"
    volumes:
      - /etc/docker-compose/gitlab:/custom-entrypoint
      - /etc/gitlab:/etc/gitlab
      - /home/gitlab:/var/opt/gitlab
    labels:
      - "traefik.backend=gitlab"
      - "traefik.port=80"
      - "traefik.frontend.rule=PathPrefix:/gitlab/"
      - "traefik.frontend.passHostHeader=true"

Oracle Database Docker Container

docker login container-registry.oracle.com
docker pull container-registry.oracle.com/database/enterprise:12.2.0.1-slim

Oracle Database in Docker Composer

  • prepare a data-store directory
# create a local directory that will be mounted into the container as a volume to store Oracle data files
mkdir -p /home/oracledb
# allow to use the directory by all users (Oracle container will use UID:GID 54321:54321)
chmod 1777 /home/oracledb
  • create a docker-compose.yml to set
    • a redirection of port 1521 for Oracle client connections over Oracle’s SQL*Net protocol
    • an access to port 5500 for Oracle XML DB via HTTPs by traefik (there is no XML DB / Enterprise Manager is the slim version, so this port will not be utilised anyway)
    • the local directory mounted into the container as a volume to store data files
    • environment variables for the initial configuration of the database
version: '3'
services:
  oracledb:
    image: container-registry.oracle.com/database/enterprise:12.2.0.1-slim
    ports:
      - "1521:1521"
    volumes:
      - /home/oracledb:/ORCL
    environment:
      # This parameter changes the ORACLE_SID of the database. This variable is optional and the default value is set to ORCLCDB.
      # NOTE: use default SID or repeat; renaming SID often invokes DBNEWID which is sucessfull, however, PDB cannot be created due to a read error on file "/u02/app/oracle/oradata/ORCL/pdbseed/system01.dbf"
      - DB_SID=ORCLCDB
      # This parameter modifies the name of the PDB. This variable is optional and the default value is set to ORCLPDB1.
      - DB_PDB=ORCLPDB
      # This parameter sets the memory requirement for the Oracle Server. This value determines the amount of memory to be allocated for SGA and PGA. This variable is optional and the default value is set to 2GB.
      - DB_MEMORY=8GB
      # This parameter sets the domain to be used for Database Server. The default value is localdomain.
      - DB_DOMAIN=myhostname.mydomain
      # This parameter sets the initial password for SYS and SYSTEM global users in CDB, and local admin user SYS1 of PDB database.
      # NOTE: the initial password must be similar to the default password Oradoc_db1, it must contain at least 8 characters and at least 1 digit and 1 special symbol.
      - DB_PASSWD=OraInit_some0initial0password
    labels:
      - "traefik.backend=oracledb"
      - "traefik.port=5500"
      - "traefik.protocol=https"
      - "traefik.frontend.rule=PathPrefixStrip:/oracledb/"
  • start the docker container via docker-compose
docker-compose down --volumes
rm -rf /home/oracledb/*
docker-compose up -d
docker-compose logs -f oracledb
  • change a password for SYS and SYSTEM users from the initial default password and show Oracle TNS configuration for connections from outside the container using sqlplus
docker-compose exec oracledb bash --login -c 'echo "alter user system identified by mynewpassword;" | sqlplus "sys/${DB_PASSWD}@orclcdb as sysdba"'
docker-compose exec oracledb bash --login -c 'echo "alter user sys1 identified by mynewpassword;" | sqlplus "sys/${DB_PASSWD}@orclpdb as sysdba"'
docker-compose exec oracledb bash --login -c 'echo "alter user sys identified by mynewpassword;" | sqlplus "sys/${DB_PASSWD}@orclcdb as sysdba"'
docker-compose exec oracledb bash --login -c 'exec cat ${TNS_ADMIN}/tnsnames.ora'

Known Bugs

  • the initial password must be similar to the default password, i.e., it must contain at least 8 characters and at least 1 digit and 1 special symbol, otherwise login actions fail; use a complex password
  • renaming SID often invokes DBNEWID which is sucessfull, however, PDB cannot be created due to a read error on file “/u02/app/oracle/oradata/ORCL/pdbseed/system01.dbf”; just repeat and use the default SID
  • sqlplus / as sysdba does not allow alter user ... (it causes a fatal error); connect into PCD or PDB with a password and submit the alter statement from there

Alpine SSHd Docker Container

  • create directory for a custom entrypoint script
mkdir -p /etc/docker-compose/alpine-sshd
  • create the entrypoint script /etc/docker-compose/alpine-sshd/docker-entrypoint.sh
#!/bin/sh

apk --no-cache add openssh
sed -i 's/^#PermitRootLogin.*$/PermitRootLogin yes/' /etc/ssh/sshd_config
if [[ ! -z ${ROOT_PASSWORD} ]] && [[ "${ROOT_PASSWORD}" != "root" ]]; then
    echo "root:${ROOT_PASSWORD}" | chpasswd
fi
ssh-keygen -A

# do not detach (-D), log to stderr (-e), passthrough other arguments
exec /usr/sbin/sshd -D -e
  • allow execution of the entrypoint script
chmod 755 /etc/docker-compose/alpine-sshd/*.sh
  • create a docker-compose.yml
version: '3'
services:
  alpine-sshd:
    image: alpine:latest
    entrypoint: /custom-entrypoint/docker-entrypoint.sh
    ports:
      - "2223:22"
    environment:
      - ROOT_PASSWORD=any2initial@root!password2without^quotation.marks
    volumes:
      - /etc/docker-compose/alpine-sshd:/custom-entrypoint
    labels:
      - "traefik.enable=false"

Apache Cassandra Docker Container

  • prepare data storage and entrypoint directories
mkdir -p /home/cassandra /etc/docker-compose/cassandra/initdb.d
chmod 1777 /home/cassandra
  • add a new entrypoint script /etc/docker-compose/cassandra/docker-entrypoint.sh
#!/bin/bash
#
# Fix ISSUE: os::commit_memory failed; error=Operation not permitted
# https://en.wikibooks.org/wiki/Grsecurity/Application-specific_Settings#Java
#

set -e

if [[ ! -e /usr/bin/setfattr ]]; then
    apt-get update
    apt-get install -y attr
fi

setfattr -n user.pax.flags -v em $(which java) $(which javaws)

#
# Initialise keyspaces
# (use CREATE * IF NOT EXISTS to prevent errors on repeated execution; otherwise, the loop will run infinitely)
#
for I in ${0%/*}/initdb.d/*.cql; do
    [[ -r "${I}" ]] && echo "${0}: running CQL script ${I}" && until cqlsh -f "${I}"; do echo "Error! Cassandra may be unavailable - sleeping and will try again." >&2; sleep 2; done &
done

exec /docker-entrypoint.sh ${@}
  • allow execution of the entrypoint script
chmod 755 /etc/docker-compose/cassandra/*.sh
  • create a docker-compose.yml
version: '3'
services:
  cassandra:
    image: cassandra:latest
    restart: always
    entrypoint: /custom-entrypoint/docker-entrypoint.sh
    environment:
      # IP address to listen for incoming connections on, cassandra.yaml:listen_address
      - CASSANDRA_LISTEN_ADDRESS=auto
      # IP address to advertise to other nodes, cassandra.yaml:broadcast_address,broadcast_rpc_address
      - CASSANDRA_BROADCAST_ADDRESS=auto
      # address to bind the thrift rpc server to, cassandra.yaml:rpc_address
      - CASSANDRA_RPC_ADDRESS=0.0.0.0
      # controlling if the thrift rpc server is started, cassandra.yaml:start_rpc
      - CASSANDRA_START_RPC=
      # the comma-separated list of IP addresses used by gossip for bootstrapping new nodes joining a cluster, cassandra.yaml:seed_provider
      - CASSANDRA_SEEDS=
      # the name of the cluster and must be the same for all nodes in the cluster, cassandra.yaml:cluster_name
      - CASSANDRA_CLUSTER_NAME=
      # number of tokens for this node, cassandra.yaml:cassandra.yaml
      - CASSANDRA_NUM_TOKENS=
      # the snitch implementation this node will use, cassandra.yaml:endpoint_snitch
      - CASSANDRA_ENDPOINT_SNITCH=
      # the datacenter name of this node, cassandra-rackdc.properties:dc (CASSANDRA_ENDPOINT_SNITCH must use the "GossipingPropertyFileSnitch")
      - CASSANDRA_DC=
      # the rack name of this node, cassandra-rackdc.properties:rack (CASSANDRA_ENDPOINT_SNITCH must use the "GossipingPropertyFileSnitch")
      - CASSANDRA_RACK=
    volumes:
      - /etc/docker-compose/cassandra:/custom-entrypoint
      - /home/cassandra:/var/lib/cassandra
    labels:
      - "traefik.enable=false"

Apache Kafka and Apache ZooKeeper Docker Containers

  • prepare entrypoint directories
mkdir -p /etc/docker-compose/zookeeper /etc/docker-compose/kafka
  • add new entrypoint script /etc/docker-compose/zookeeper/docker-entrypoint.sh
#!/bin/bash
#
# Fix ISSUE: os::commit_memory failed; error=Operation not permitted
# https://en.wikibooks.org/wiki/Grsecurity/Application-specific_Settings#Java
#

set -e

if [[ ! -e /usr/bin/setfattr ]]; then
    apt-get update
    apt-get install -y attr
fi

setfattr -n user.pax.flags -v em $(which java) $(which javaws)

exec ${@:-sh -c '/usr/sbin/sshd && bash /usr/bin/start-zk.sh'}
  • download wait-for helper script
wget -O /etc/docker-compose/kafka/wait-for.sh https://raw.githubusercontent.com/eficode/wait-for/master/wait-for
  • add new entrypoint script /etc/docker-compose/kafka/docker-entrypoint.sh
#!/bin/sh
#
# Fix ISSUE: os::commit_memory failed; error=Operation not permitted
# https://en.wikibooks.org/wiki/Grsecurity/Application-specific_Settings#Java
#

set -e

[[ ! -e /usr/bin/setfattr ]] && apk --no-cache add attr

setfattr -n user.pax.flags -v em $(which java) $(which javaws)

# wait-for: https://raw.githubusercontent.com/eficode/wait-for/master/wait-for
exec ${0%/*}/wait-for.sh "${KAFKA_ZOOKEEPER_CONNECT}" -- ${@:-start-kafka.sh}
  • allow execution of the entrypoint and helper scripts
chmod 755 /etc/docker-compose/zookeeper/*.sh /etc/docker-composer/kafka/*.sh
  • create a docker-compose.yml
version: '3'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    entrypoint: /custom-entrypoint/docker-entrypoint.sh
    volumes:
      - /etc/docker-compose/zookeeper:/custom-entrypoint
    labels:
      - "traefik.enable=false"
  kafka:
    image: wurstmeister/kafka
    depends_on:
      - zookeeper
    entrypoint: /custom-entrypoint/docker-entrypoint.sh
    environment:
      # set ZooKeeper connection
      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
      # autodetect the advertised hostname (contrary to setting it explicitly using KAFKA_ADVERTISED_HOST_NAME)
      - HOSTNAME_COMMAND=route -n | awk '/UG[ \t]/{print $$2}'
      # autogenerate the broker id to allow scaling up and down (use the --no-recreate option of docker-compose to ensure that containers are not re-created and thus keep their names and ids)
      - BROKER_ID_COMMAND=
      # set the broker rack affinity
      - KAFKA_BROKER_RACK=
      # automatically create topics in Kafka during creation (e.g., Topic 1 will have 1 partition and 3 replicas, Topic 2 will have 1 partition, 1 replica and a cleanup.policy set to compact)
      #- KAFKA_CREATE_TOPICS="Topic1:1:3,Topic2:1:1:compact"
    volumes:
      - /etc/docker-compose/kafka:/custom-entrypoint
    labels:
      - "traefik.enable=false"

 Share!

 
comments powered by Disqus