From c8e9dec549e01bf1a5397a384809a7390db8df6c Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 20:09:36 +0530 Subject: [PATCH 01/14] complete rewrite of existing feature set Closes #40 --- Changelog.md | 3 + Dockerfile | 11 +- entrypoint.sh | 462 +++++++++++++++++++++++++++++++------------------- 3 files changed, 296 insertions(+), 180 deletions(-) diff --git a/Changelog.md b/Changelog.md index 958e6ec..8614d00 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,8 @@ # Changelog +**latest** +- complete rewrite + **9.4-2** - added replication options diff --git a/Dockerfile b/Dockerfile index 0315f28..ac0cf45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,14 +7,16 @@ ENV PG_VERSION=9.4 \ PG_RUNDIR=/run/postgresql \ PG_LOGDIR=/var/log/postgresql -ENV PG_BINDIR="/usr/lib/postgresql/${PG_VERSION}/bin" \ - PG_CONFDIR="${PG_HOME}/${PG_VERSION}/main" \ - PG_DATADIR="${PG_HOME}/${PG_VERSION}/main" +ENV PG_BINDIR=/usr/lib/postgresql/${PG_VERSION}/bin \ + PG_DATADIR=${PG_HOME}/${PG_VERSION}/main RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ && echo 'deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main' > /etc/apt/sources.list.d/pgdg.list \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-${PG_VERSION} postgresql-client-${PG_VERSION} postgresql-contrib-${PG_VERSION} \ + && ln -sf ${PG_DATADIR}/postgresql.conf /etc/postgresql/${PG_VERSION}/main/postgresql.conf \ + && ln -sf ${PG_DATADIR}/pg_hba.conf /etc/postgresql/${PG_VERSION}/main/pg_hba.conf \ + && ln -sf ${PG_DATADIR}/pg_ident.conf /etc/postgresql/${PG_VERSION}/main/pg_ident.conf \ && rm -rf ${PG_HOME} \ && rm -rf /var/lib/apt/lists/* @@ -23,4 +25,5 @@ RUN chmod 755 /sbin/entrypoint.sh EXPOSE 5432/tcp VOLUME ["${PG_HOME}", "${PG_RUNDIR}"] -CMD ["/sbin/entrypoint.sh"] +WORKDIR ${PG_HOME} +ENTRYPOINT ["/sbin/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh index ed106d6..6dbfe6a 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,31 +1,33 @@ #!/bin/bash set -e -# set this env variable to true to enable a line in the -# pg_hba.conf file to trust samenet. this can be used to connect -# from other containers on the same host without authentication +PSQL_MODE=${PSQL_MODE:-} +PSQL_SSLMODE=${PSQL_SSLMODE:-} PSQL_TRUST_LOCALNET=${PSQL_TRUST_LOCALNET:-false} -DB_NAME=${DB_NAME:-} -DB_USER=${DB_USER:-} -DB_PASS=${DB_PASS:-} -DB_LOCALE=${DB_LOCALE:-C} -DB_UNACCENT=${DB_UNACCENT:false} - -# by default postgresql will start up as a standalone instance. -# set this environment variable to master, slave or snapshot to use replication features. -# "snapshot" will create a point in time backup of a master instance. -PSQL_MODE=${PSQL_MODE:-standalone} - REPLICATION_USER=${REPLICATION_USER:-} REPLICATION_PASS=${REPLICATION_PASS:-} REPLICATION_HOST=${REPLICATION_HOST:-} -REPLICATION_PORT=${REPLICATION_PORT:-5432} +REPLICATION_PORT=${REPLICATION_PORT:-} -# set this env variable to "require" to enable encryption and "verify-full" for verification. -PSQL_SSLMODE=${PSQL_SSLMODE:-disable} +DB_NAME=${DB_NAME:-} +DB_USER=${DB_USER:-} +DB_PASS=${DB_PASS:-} -map_postgres_uid() { +DB_LOCALE=${DB_LOCALE:-C} +DB_UNACCENT=${DB_UNACCENT:-false} + +PG_CONF=${PG_DATADIR}/postgresql.conf +PG_HBA_CONF=${PG_DATADIR}/pg_hba.conf +PG_IDENT_CONF=${PG_DATADIR}/pg_ident.conf +PG_RECOVERY_CONF=${PG_DATADIR}/recovery.conf + +## Execute command as PG_USER +exec_as_postgres() { + sudo -HEu ${PG_USER} "$@" +} + +map_uidgid() { USERMAP_ORIG_UID=$(id -u ${PG_USER}) USERMAP_ORIG_GID=$(id -g ${PG_USER}) USERMAP_GID=${USERMAP_GID:-${USERMAP_UID:-$USERMAP_ORIG_GID}} @@ -33,202 +35,310 @@ map_postgres_uid() { if [[ ${USERMAP_UID} != ${USERMAP_ORIG_UID} ]] || [[ ${USERMAP_GID} != ${USERMAP_ORIG_GID} ]]; then echo "Adapting uid and gid for ${PG_USER}:${PG_USER} to $USERMAP_UID:$USERMAP_GID" groupmod -g ${USERMAP_GID} ${PG_USER} - sed -i -e "s/:${USERMAP_ORIG_UID}:${USERMAP_GID}:/:${USERMAP_UID}:${USERMAP_GID}:/" /etc/passwd + sed -i -e "s|:${USERMAP_ORIG_UID}:${USERMAP_GID}:|:${USERMAP_UID}:${USERMAP_GID}:|" /etc/passwd fi } -create_data_dir() { +locale_gen() { + if [[ $DB_LOCALE != C ]]; then + echo "Generating locale \"${DB_LOCALE}\"..." + locale-gen ${DB_LOCALE} >/dev/null + fi +} + +create_datadir() { + echo "Initializing datadir..." mkdir -p ${PG_HOME} - chmod -R 0700 ${PG_HOME} + if [[ -d ${PG_DATADIR} ]]; then + find ${PG_DATADIR} -type f -exec chmod 0600 {} \; + find ${PG_DATADIR} -type d -exec chmod 0700 {} \; + fi chown -R ${PG_USER}:${PG_USER} ${PG_HOME} } -create_log_dir() { +create_logdir() { + echo "Initializing logdir..." mkdir -p ${PG_LOGDIR} chmod -R 1775 ${PG_LOGDIR} chown -R root:${PG_USER} ${PG_LOGDIR} } -create_run_dir() { +create_rundir() { + echo "Initializing rundir..." mkdir -p ${PG_RUNDIR} ${PG_RUNDIR}/${PG_VERSION}-main.pg_stat_tmp chmod -R 0755 ${PG_RUNDIR} chmod g+s ${PG_RUNDIR} chown -R ${PG_USER}:${PG_USER} ${PG_RUNDIR} } -map_postgres_uid -create_data_dir -create_log_dir -create_run_dir - -cd ${PG_HOME} - -# initialize PostgreSQL data directory -if [[ ! -d ${PG_DATADIR} ]]; then - if [[ ${PSQL_MODE} == slave || ${PSQL_MODE} == snapshot ]]; then - echo "Replicating database..." - if [[ ${PSQL_MODE} == snapshot ]]; then - sudo -Hu ${PG_USER} \ - PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ - -h ${REPLICATION_HOST} -p ${REPLICATION_PORT} -U ${REPLICATION_USER} -w -x -v -P - elif [[ ${PSQL_MODE} == slave ]]; then - # Setup streaming replication. - sudo -Hu ${PG_USER} \ - PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ - -h ${REPLICATION_HOST} -p ${REPLICATION_PORT} -U ${REPLICATION_USER} -X stream -w -v -P - echo "Setting up hot standby configuration..." - sudo -Hu ${PG_USER} sed -i "s|^#hot_standby = .*|hot_standby = on|" ${PG_CONFDIR}/postgresql.conf - sudo -Hu ${PG_USER} touch ${PG_DATADIR}/recovery.conf - ( echo "standby_mode = 'on'"; - echo "primary_conninfo = 'host=${REPLICATION_HOST} port=${REPLICATION_PORT} user=${REPLICATION_USER} password=${REPLICATION_PASS} sslmode=${PSQL_SSLMODE}'"; - echo "trigger_file = '/tmp/postgresql.trigger'" ) > ${PG_DATADIR}/recovery.conf - fi - - else - # check if we need to perform data migration - PG_OLD_VERSION=$(find ${PG_HOME}/[0-9].[0-9]/main -maxdepth 1 -name PG_VERSION 2>/dev/null | sort -r | head -n1 | cut -d'/' -f5) - - if [[ $DB_LOCALE != C ]]; then - echo "Generating required locale \"${DB_LOCALE}\"..." - locale-gen ${DB_LOCALE} >/dev/null - fi - - echo "Initializing database..." - sudo -Hu ${PG_USER} ${PG_BINDIR}/initdb --pgdata=${PG_DATADIR} \ - --username=${PG_USER} --encoding=unicode --locale=${DB_LOCALE} --auth=trust >/dev/null - fi -fi - -if [[ -n ${PG_OLD_VERSION} ]]; then - echo "Migrating postgresql ${PG_OLD_VERSION} data..." - PG_OLD_CONFDIR="${PG_HOME}/${PG_OLD_VERSION}/main" - PG_OLD_BINDIR="/usr/lib/postgresql/${PG_OLD_VERSION}/bin" - PG_OLD_DATADIR="${PG_HOME}/${PG_OLD_VERSION}/main" - - # backup ${PG_OLD_DATADIR} to avoid data loss - PG_BKP_SUFFIX=$(date +%Y%m%d%H%M%S) - echo "Backing up ${PG_OLD_DATADIR} to ${PG_OLD_DATADIR}.${PG_BKP_SUFFIX}..." - cp -a ${PG_OLD_DATADIR} ${PG_OLD_DATADIR}.${PG_BKP_SUFFIX} - - echo "Installing postgresql-${PG_OLD_VERSION}..." - apt-get update - DEBIAN_FRONTEND=noninteractive apt-get install postgresql-${PG_OLD_VERSION} postgresql-client-${PG_OLD_VERSION} - rm -rf /var/lib/apt/lists/* - - # migrate ${PG_OLD_VERSION} data - echo "Migration in progress. This could take a while, please be patient..." - sudo -Hu ${PG_USER} ${PG_BINDIR}/pg_upgrade \ - -b ${PG_OLD_BINDIR} -B ${PG_BINDIR} \ - -d ${PG_OLD_DATADIR} -D ${PG_DATADIR} \ - -o "-c config_file=${PG_OLD_CONFDIR}/postgresql.conf --hba_file=${PG_OLD_CONFDIR}/pg_hba.conf --ident_file=${PG_OLD_CONFDIR}/pg_ident.conf" \ - -O "-c config_file=${PG_CONFDIR}/postgresql.conf --hba_file=${PG_CONFDIR}/pg_hba.conf --ident_file=${PG_CONFDIR}/pg_ident.conf" >/dev/null -fi - -if [[ ${PSQL_SSLMODE} == disable ]]; then - sudo -Hu ${PG_USER} sed -i "s|^[#]*[ ]*ssl = .*|ssl = off|" ${PG_CONFDIR}/postgresql.conf -else - sudo -Hu ${PG_USER} sed -i "s|^[#]*[ ]*ssl = .*|ssl = on|" ${PG_CONFDIR}/postgresql.conf -fi - -# Change DSM from `posix' to `sysv' if we are inside an lx-brand container -if [[ $(uname -v) == "BrandZ virtual linux" ]]; then - sed 's/\(dynamic_shared_memory_type = \)posix/\1sysv/' -i ${PG_CONFDIR}/postgresql.conf -fi - -# listen on all interfaces -sudo -Hu ${PG_USER} sed -i "s|^[#]*[ ]*listen_addresses = .*|listen_addresses = '*'|" ${PG_CONFDIR}/postgresql.conf - -if [[ ${PSQL_TRUST_LOCALNET} == true ]]; then - if ! grep -q "host \+all \+all \+samenet \+trust" ${PG_CONFDIR}/pg_hba.conf; then - echo "Enabling trust samenet in pg_hba.conf..." - echo "host all all samenet trust" >> ${PG_CONFDIR}/pg_hba.conf - fi -fi - -# allow remote connections to postgresql database -if ! grep -q "host \+all \+all \+0.0.0.0/0 \+md5" ${PG_CONFDIR}/pg_hba.conf; then - echo "host all all 0.0.0.0/0 md5" >> ${PG_CONFDIR}/pg_hba.conf -fi - -# allow replication connections to the database -if [[ -n ${REPLICATION_USER} ]]; then - if [[ ${PSQL_SSLMODE} == disable ]]; then - if ! grep -q "host \+replication \+$REPLICATION_USER \+0.0.0.0/0 \+md5" ${PG_CONFDIR}/pg_hba.conf; then - echo "host replication $REPLICATION_USER 0.0.0.0/0 md5" >> ${PG_CONFDIR}/pg_hba.conf - fi - else - if ! grep -q "hostssl \+replication \+$REPLICATION_USER \+0.0.0.0/0 \+md5" ${PG_CONFDIR}/pg_hba.conf; then - echo "hostssl replication $REPLICATION_USER 0.0.0.0/0 md5" >> ${PG_CONFDIR}/pg_hba.conf +set_postgresql_param() { + local key=${1} + local value=${2} + if [[ -n ${value} ]]; then + local current=$(exec_as_postgres sed -n -e "s/^\("${key}" = '\)\([^ ']*\)\(.*\)$/\2/p" ${PG_CONF}) + if [[ "${current}" != "${value}" ]]; then + echo "‣ Setting postgresql.conf parameter: ${key} = '${value}'" + exec_as_postgres sed -i "s|^[#]*[ ]*"${key}" = .*|"${key}" = '"${value}"'|" ${PG_CONF} fi fi -fi +} -if [[ ${PSQL_MODE} == master ]]; then - if [[ -n ${REPLICATION_USER} ]]; then - echo "Supporting hot standby..." - sudo -Hu ${PG_USER} sed -i "s|^#wal_level = .*|wal_level = hot_standby|" ${PG_CONFDIR}/postgresql.conf - sudo -Hu ${PG_USER} sed -i "s|^#max_wal_senders = .*|max_wal_senders = 3|" ${PG_CONFDIR}/postgresql.conf - sudo -Hu ${PG_USER} sed -i "s|^#checkpoint_segments = .*|checkpoint_segments = 8|" ${PG_CONFDIR}/postgresql.conf - sudo -Hu ${PG_USER} sed -i "s|^#wal_keep_segments = .*|wal_keep_segments = 8|" ${PG_CONFDIR}/postgresql.conf - fi -fi - -# Hot standby (slave and snapshot) servers can ignore the following code. -if [[ ${PSQL_MODE} == standalone || ${PSQL_MODE} == master ]]; then - if [[ -n ${REPLICATION_USER} ]]; then - if [[ -z ${REPLICATION_PASS} ]]; then - echo "" - echo "WARNING: " - echo " Please specify a password for replication user \"${REPLICATION_USER}\". Skipping user creation..." - echo "" - DB_USER= - else - echo "Creating user \"${REPLICATION_USER}\"..." - echo "CREATE ROLE \"${REPLICATION_USER}\" WITH REPLICATION LOGIN ENCRYPTED PASSWORD '${REPLICATION_PASS}';" | - sudo -Hu ${PG_USER} ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} \ - -c config_file=${PG_CONFDIR}/postgresql.conf --hba_file=${PG_CONFDIR}/pg_hba.conf --ident_file=${PG_CONFDIR}/pg_ident.conf >/dev/null +set_recovery_param() { + local key=${1} + local value=${2} + if [[ -n ${value} ]]; then + local current=$(exec_as_postgres sed -n -e "s/^\(.*\)\("${key}"=\)\([^ ']*\)\(.*\)$/\3/p" ${PG_RECOVERY_CONF}) + if [[ "${current}" != "${value}" ]]; then + echo "Updating primary_conninfo ${key}..." + exec_as_postgres sed -i "s|"${key}"=[^ ']*|"${key}"="${value}"|" ${PG_RECOVERY_CONF} fi fi +} +set_hba_param() { + local value=${1} + if ! grep -q "$(sed "s| | \\\+|g" <<< ${value})" ${PG_HBA_CONF}; then + echo "${value}" >> ${PG_HBA_CONF} + fi +} + +configure_hot_standby() { + case ${PSQL_MODE} in + slave|snapshot) ;; + *) + echo "Configuring hot standby..." + set_postgresql_param "wal_level" "hot_standby" + set_postgresql_param "max_wal_senders" "16" + set_postgresql_param "checkpoint_segments" "8" + set_postgresql_param "wal_keep_segments" "32" + set_postgresql_param "hot_standby" "on" + ;; + esac +} + +initialize_database() { + if [[ ! -f ${PG_DATADIR}/PG_VERSION ]]; then + case ${PSQL_MODE} in + slave|snapshot) + # default params + REPLICATION_PORT=${REPLICATION_PORT:-5432} + PSQL_SSLMODE=${PSQL_SSLMODE:-disable} + + if [[ -z $REPLICATION_HOST ]]; then + echo "ERROR! Cannot continue without the REPLICATION_HOST. Exiting..." + exit 1 + fi + + if [[ -z $REPLICATION_USER ]]; then + echo "ERROR! Cannot continue without the REPLICATION_USER. Exiting..." + exit 1 + fi + + if [[ -z $REPLICATION_PASS ]]; then + echo "ERROR! Cannot continue without the REPLICATION_PASS. Exiting..." + exit 1 + fi + + echo -n "Waiting for $REPLICATION_HOST to accept connections (60s timeout)" + timeout=60 + while ! ${PG_BINDIR}/pg_isready -h $REPLICATION_HOST -p $REPLICATION_PORT -t 1 >/dev/null 2>&1 + do + timeout=$(expr $timeout - 1) + if [[ $timeout -eq 0 ]]; then + echo "Timeout! Exiting..." + exit 1 + fi + echo -n "." + sleep 1 + done + echo + + case ${PSQL_MODE} in + slave) + echo "Replicating initial data from $REPLICATION_HOST..." + exec_as_postgres PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ + -h ${REPLICATION_HOST} -p ${REPLICATION_PORT} -U ${REPLICATION_USER} -X stream -w >/dev/null + ;; + snapshot) + echo "Generating a snapshot data on $REPLICATION_HOST..." + exec_as_postgres PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ + -h ${REPLICATION_HOST} -p ${REPLICATION_PORT} -U ${REPLICATION_USER} -X fetch -w >/dev/null + esac + ;; + *) + echo "Initializing database..." + PG_OLD_VERSION=$(find ${PG_HOME}/[0-9].[0-9]/main -maxdepth 1 -name PG_VERSION 2>/dev/null | grep -v $PG_VERSION | sort -r | head -n1 | cut -d'/' -f5) + if [[ -n ${PG_OLD_VERSION} ]]; then + echo "‣ Migrating PostgreSQL ${PG_OLD_VERSION} data to ${PG_VERSION}..." + + # protect the existing data from being altered by apt-get + mv ${PG_HOME}/${PG_OLD_VERSION} ${PG_HOME}/${PG_OLD_VERSION}.migrating + + echo "‣ Installing PostgreSQL ${PG_OLD_VERSION}..." + if ! ( apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-${PG_OLD_VERSION} postgresql-client-${PG_OLD_VERSION} ) >/dev/null; then + echo "ERROR! Failed to install PostgreSQL ${PG_OLD_VERSION}. Exiting..." + # first move the old data back + rm -rf ${PG_HOME}/${PG_OLD_VERSION} + mv ${PG_HOME}/${PG_OLD_VERSION}.migrating ${PG_HOME}/${PG_OLD_VERSION} + exit 1 + fi + rm -rf /var/lib/apt/lists/* + + # we're ready to migrate, move back the old data and remove the trap + rm -rf ${PG_HOME}/${PG_OLD_VERSION} + mv ${PG_HOME}/${PG_OLD_VERSION}.migrating ${PG_HOME}/${PG_OLD_VERSION} + fi + + exec_as_postgres ${PG_BINDIR}/initdb --pgdata=${PG_DATADIR} \ + --username=${PG_USER} --encoding=unicode --locale=${DB_LOCALE} --auth=trust >/dev/null + + if [[ -n ${PG_OLD_VERSION} ]]; then + PG_OLD_BINDIR=/usr/lib/postgresql/${PG_OLD_VERSION}/bin + PG_OLD_DATADIR=${PG_HOME}/${PG_OLD_VERSION}/main + PG_OLD_CONF=${PG_OLD_DATADIR}/postgresql.conf + PG_OLD_HBA_CONF=${PG_OLD_DATADIR}/pg_hba.conf + PG_OLD_IDENT_CONF=${PG_OLD_DATADIR}/pg_ident.conf + + echo -n "‣ Migration in progress. Please be patient..." + exec_as_postgres ${PG_BINDIR}/pg_upgrade \ + -b ${PG_OLD_BINDIR} -B ${PG_BINDIR} \ + -d ${PG_OLD_DATADIR} -D ${PG_DATADIR} \ + -o "-c config_file=${PG_OLD_CONF} --hba_file=${PG_OLD_HBA_CONF} --ident_file=${PG_OLD_IDENT_CONF}" \ + -O "-c config_file=${PG_CONF} --hba_file=${PG_HBA_CONF} --ident_file=${PG_IDENT_CONF}" >/dev/null + echo + fi + ;; + esac + + # configure path to data_directory + set_postgresql_param "data_directory" "${PG_DATADIR}" + + # listen on all interfaces + set_postgresql_param "listen_addresses" "*" + + # allow remote connections to postgresql database + set_hba_param "host all all 0.0.0.0/0 md5" + + configure_hot_standby + + # Change DSM from `posix' to `sysv' if we are inside an lx-brand container + if [[ $(uname -v) == "BrandZ virtual linux" ]]; then + set_postgresql_param "dynamic_shared_memory_type" "sysv" + fi + fi +} + +trust_localnet() { + if [[ ${PSQL_TRUST_LOCALNET} == true ]]; then + echo "Trusting connections from the local network..." + set_hba_param "host all all samenet trust" + fi +} + +create_user() { if [[ -n ${DB_USER} ]]; then if [[ -z ${DB_PASS} ]]; then - echo "" - echo "WARNING: " - echo " Please specify a password for \"${DB_USER}\". Skipping user creation..." - echo "" - DB_USER= - else - echo "Creating user \"${DB_USER}\"..." - echo "CREATE ROLE \"${DB_USER}\" with LOGIN CREATEDB PASSWORD '${DB_PASS}';" | - sudo -Hu ${PG_USER} ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} \ - -c config_file=${PG_CONFDIR}/postgresql.conf --hba_file=${PG_CONFDIR}/pg_hba.conf --ident_file=${PG_CONFDIR}/pg_ident.conf >/dev/null + echo "ERROR! Please specify a password for DB_USER in DB_PASS. Exiting..." + exit 1 fi + echo "Creating database user: ${DB_USER}" + echo "CREATE ROLE \"${DB_USER}\" with LOGIN CREATEDB PASSWORD '${DB_PASS}';" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 fi +} +create_database() { if [[ -n ${DB_NAME} ]]; then - for db in $(awk -F',' '{for (i = 1 ; i <= NF ; i++) print $i}' <<< "${DB_NAME}"); do - echo "Creating database \"${db}\"..." - echo "CREATE DATABASE \"${db}\";" | \ - sudo -Hu ${PG_USER} ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} \ - -c config_file=${PG_CONFDIR}/postgresql.conf --hba_file=${PG_CONFDIR}/pg_hba.conf --ident_file=${PG_CONFDIR}/pg_ident.conf >/dev/null + echo -n "Creating database(s): " + for database in $(awk -F',' '{for (i = 1 ; i <= NF ; i++) print $i}' <<< "${DB_NAME}"); do + echo -n "${database} " + echo "CREATE DATABASE \"${database}\";" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 if [[ ${DB_UNACCENT} == true ]]; then - echo "Installing unaccent extension..." echo "CREATE EXTENSION IF NOT EXISTS unaccent;" | \ - sudo -Hu ${PG_USER} ${PG_BINDIR}/postgres --single ${db} -D ${PG_DATADIR} \ - -c config_file=${PG_CONFDIR}/postgresql.conf --hba_file=${PG_CONFDIR}/pg_hba.conf --ident_file=${PG_CONFDIR}/pg_ident.conf >/dev/null + exec_as_postgres ${PG_BINDIR}/postgres --single ${database} -D ${PG_DATADIR} >/dev/null 2>&1 fi if [[ -n ${DB_USER} ]]; then - echo "Granting access to database \"${db}\" for user \"${DB_USER}\"..." - echo "GRANT ALL PRIVILEGES ON DATABASE \"${db}\" to \"${DB_USER}\";" | - sudo -Hu ${PG_USER} ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} \ - -c config_file=${PG_CONFDIR}/postgresql.conf --hba_file=${PG_CONFDIR}/pg_hba.conf --ident_file=${PG_CONFDIR}/pg_ident.conf >/dev/null + echo "GRANT ALL PRIVILEGES ON DATABASE \"${database}\" to \"${DB_USER}\";" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 fi done + echo fi +} + +create_replication_user() { + case $PSQL_MODE in + slave|snapshot) ;; # replication user can only be created on the master + *) + if [[ -n ${REPLICATION_USER} ]]; then + if [[ -z ${REPLICATION_PASS} ]]; then + echo "ERROR! Please specify a password for REPLICATION_USER in REPLICATION_PASS. Exiting..." + exit 1 + fi + + echo "Creating replication user: ${REPLICATION_USER}" + echo "CREATE ROLE \"${REPLICATION_USER}\" WITH REPLICATION LOGIN ENCRYPTED PASSWORD '${REPLICATION_PASS}';" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 + + set_hba_param "host replication ${REPLICATION_USER} 0.0.0.0/0 md5" + fi + ;; + esac +} + +configure_recovery() { + if [[ ! -f ${PG_RECOVERY_CONF} ]]; then + if [[ ${PSQL_MODE} == slave ]]; then + # initialize recovery.conf on the firstrun (slave only) + echo "Configuring recovery..." + exec_as_postgres touch ${PG_RECOVERY_CONF} + ( echo "standby_mode = 'on'"; + echo "primary_conninfo = 'host=${REPLICATION_HOST} port=${REPLICATION_PORT} user=${REPLICATION_USER} password=${REPLICATION_PASS} sslmode=${PSQL_SSLMODE}'"; + echo "trigger_file = '/tmp/postgresql.trigger'" ) > ${PG_RECOVERY_CONF} + fi + else + set_recovery_param "host" "${REPLICATION_HOST}" + set_recovery_param "port" "${REPLICATION_PORT}" + set_recovery_param "user" "${REPLICATION_USER}" + set_recovery_param "password" "${REPLICATION_PASS}" + set_recovery_param "sslmode" "${PSQL_SSLMODE}" + fi +} + +# allow arguments to be passed to postgres +if [[ ${1:0:1} = '-' ]]; then + EXTRA_ARGS="$@" + set -- +elif [[ ${1} == postgres || ${1} == $(which postgres) ]]; then + EXTRA_ARGS="${@:2}" + set -- +fi + +# default behaviour is to launch postgres +if [[ -z ${1} ]]; then + + map_uidgid + locale_gen + + create_datadir + create_logdir + create_rundir + + initialize_database + trust_localnet + + create_user + create_database + create_replication_user + configure_recovery + + echo "Starting PostgreSQL ${PG_VERSION}..." + exec start-stop-daemon --start --chuid ${PG_USER}:${PG_USER} \ + --exec ${PG_BINDIR}/postgres -- -D ${PG_DATADIR} ${EXTRA_ARGS} +else + exec "$@" fi -echo "Starting PostgreSQL server..." -exec start-stop-daemon --start --chuid ${PG_USER}:${PG_USER} --exec ${PG_BINDIR}/postgres -- -D ${PG_DATADIR} \ - -c config_file=${PG_CONFDIR}/postgresql.conf --hba_file=${PG_CONFDIR}/pg_hba.conf --ident_file=${PG_CONFDIR}/pg_ident.conf From de5295ffa7c9e269985f72206d993d8962b0ccb4 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 20:30:55 +0530 Subject: [PATCH 02/14] `PSQL_TRUST_LOCALNET` config parameter renamed to `PG_TRUST_LOCALNET` --- Changelog.md | 1 + README.md | 8 ++++---- entrypoint.sh | 6 ++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index 8614d00..46518d9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ **latest** - complete rewrite +- `PSQL_TRUST_LOCALNET` config parameter renamed to `PG_TRUST_LOCALNET` **9.4-2** - added replication options diff --git a/README.md b/README.md index 5e526c7..5eadc87 100644 --- a/README.md +++ b/README.md @@ -150,13 +150,13 @@ will create a user *dbuser* with the password *dbpass*. It will also create a da The `DB_LOCALE` environment variable can be used to configure the locale used for database creation. Its default value is set to C. -The `PSQL_TRUST_LOCALNET` environment variable can be used to configure postgres to trust connections on the same network. This is handy for other containers to connect without authentication. To enable this behavior, set `PSQL_TRUST_LOCALNET` to `true`. +The `PG_TRUST_LOCALNET` environment variable can be used to configure postgres to trust connections on the same network. This is handy for other containers to connect without authentication. To enable this behavior, set `PG_TRUST_LOCALNET` to `true`. For example, ```bash docker run --name postgresql -d \ - -e 'PSQL_TRUST_LOCALNET=true' \ + -e 'PG_TRUST_LOCALNET=true' \ sameersbn/postgresql:9.4-8 ``` @@ -176,7 +176,7 @@ Create a master instance ```bash docker run --name='psql-master' -it --rm \ - -e 'PSQL_MODE=master' -e 'PSQL_TRUST_LOCALNET=true' \ + -e 'PSQL_MODE=master' -e 'PG_TRUST_LOCALNET=true' \ -e 'REPLICATION_USER=replicator' -e 'REPLICATION_PASS=replicatorpass' \ -e 'DB_NAME=dbname' -e 'DB_USER=dbuser' -e 'DB_PASS=dbpass' \ sameersbn/postgresql:9.4-8 @@ -187,7 +187,7 @@ Create a streaming replication instance ```bash docker run --name='psql-slave' -it --rm \ --link psql-master:psql-master \ - -e 'PSQL_MODE=slave' -e 'PSQL_TRUST_LOCALNET=true' \ + -e 'PSQL_MODE=slave' -e 'PG_TRUST_LOCALNET=true' \ -e 'REPLICATION_HOST=psql-master' -e 'REPLICATION_PORT=5432' \ -e 'REPLICATION_USER=replicator' -e 'REPLICATION_PASS=replicatorpass' \ sameersbn/postgresql:9.4-8 diff --git a/entrypoint.sh b/entrypoint.sh index 6dbfe6a..c808ed8 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -3,7 +3,9 @@ set -e PSQL_MODE=${PSQL_MODE:-} PSQL_SSLMODE=${PSQL_SSLMODE:-} -PSQL_TRUST_LOCALNET=${PSQL_TRUST_LOCALNET:-false} + +PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility +PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} REPLICATION_USER=${REPLICATION_USER:-} REPLICATION_PASS=${REPLICATION_PASS:-} @@ -229,7 +231,7 @@ initialize_database() { } trust_localnet() { - if [[ ${PSQL_TRUST_LOCALNET} == true ]]; then + if [[ ${PG_TRUST_LOCALNET} == true ]]; then echo "Trusting connections from the local network..." set_hba_param "host all all samenet trust" fi From b6f0cba6a7ca1100a4e1c16aa45931e2951c140b Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 20:33:40 +0530 Subject: [PATCH 03/14] `PSQL_MODE` config parameter renamed to `REPLICATION_MODE` --- Changelog.md | 1 + README.md | 8 ++++---- entrypoint.sh | 13 +++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Changelog.md b/Changelog.md index 46518d9..8462a11 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,7 @@ **latest** - complete rewrite - `PSQL_TRUST_LOCALNET` config parameter renamed to `PG_TRUST_LOCALNET` +- `PSQL_MODE` config parameter renamed to `REPLICATION_MODE` **9.4-2** - added replication options diff --git a/README.md b/README.md index 5eadc87..294d867 100644 --- a/README.md +++ b/README.md @@ -168,15 +168,15 @@ host all all samenet trust # Creating a Snapshot or Slave Database -You may use the `PSQL_MODE` variable along with `REPLICATION_HOST`, `REPLICATION_PORT`, `REPLICATION_USER` and `REPLICATION_PASS` to create a snapshot of an existing database and enable stream replication. +You may use the `REPLICATION_MODE` variable along with `REPLICATION_HOST`, `REPLICATION_PORT`, `REPLICATION_USER` and `REPLICATION_PASS` to create a snapshot of an existing database and enable stream replication. -Your master database must support replication or super-user access for the credentials you specify. The `PSQL_MODE` variable should be set to `master`, for replication on your master node and `slave` or `snapshot` respectively for streaming replication or a point-in-time snapshot of a running instance. +Your master database must support replication or super-user access for the credentials you specify. The `REPLICATION_MODE` variable should be set to `master`, for replication on your master node and `slave` or `snapshot` respectively for streaming replication or a point-in-time snapshot of a running instance. Create a master instance ```bash docker run --name='psql-master' -it --rm \ - -e 'PSQL_MODE=master' -e 'PG_TRUST_LOCALNET=true' \ + -e 'REPLICATION_MODE=master' -e 'PG_TRUST_LOCALNET=true' \ -e 'REPLICATION_USER=replicator' -e 'REPLICATION_PASS=replicatorpass' \ -e 'DB_NAME=dbname' -e 'DB_USER=dbuser' -e 'DB_PASS=dbpass' \ sameersbn/postgresql:9.4-8 @@ -187,7 +187,7 @@ Create a streaming replication instance ```bash docker run --name='psql-slave' -it --rm \ --link psql-master:psql-master \ - -e 'PSQL_MODE=slave' -e 'PG_TRUST_LOCALNET=true' \ + -e 'REPLICATION_MODE=slave' -e 'PG_TRUST_LOCALNET=true' \ -e 'REPLICATION_HOST=psql-master' -e 'REPLICATION_PORT=5432' \ -e 'REPLICATION_USER=replicator' -e 'REPLICATION_PASS=replicatorpass' \ sameersbn/postgresql:9.4-8 diff --git a/entrypoint.sh b/entrypoint.sh index c808ed8..924a235 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,12 +1,13 @@ #!/bin/bash set -e -PSQL_MODE=${PSQL_MODE:-} PSQL_SSLMODE=${PSQL_SSLMODE:-} PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} +REPLICATION_MODE=${REPLICATION_MODE:-$PSQL_MODE} # backward compatibility +REPLICATION_MODE=${REPLICATION_MODE:-} REPLICATION_USER=${REPLICATION_USER:-} REPLICATION_PASS=${REPLICATION_PASS:-} REPLICATION_HOST=${REPLICATION_HOST:-} @@ -105,7 +106,7 @@ set_hba_param() { } configure_hot_standby() { - case ${PSQL_MODE} in + case ${REPLICATION_MODE} in slave|snapshot) ;; *) echo "Configuring hot standby..." @@ -120,7 +121,7 @@ configure_hot_standby() { initialize_database() { if [[ ! -f ${PG_DATADIR}/PG_VERSION ]]; then - case ${PSQL_MODE} in + case ${REPLICATION_MODE} in slave|snapshot) # default params REPLICATION_PORT=${REPLICATION_PORT:-5432} @@ -155,7 +156,7 @@ initialize_database() { done echo - case ${PSQL_MODE} in + case ${REPLICATION_MODE} in slave) echo "Replicating initial data from $REPLICATION_HOST..." exec_as_postgres PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ @@ -272,7 +273,7 @@ create_database() { } create_replication_user() { - case $PSQL_MODE in + case $REPLICATION_MODE in slave|snapshot) ;; # replication user can only be created on the master *) if [[ -n ${REPLICATION_USER} ]]; then @@ -293,7 +294,7 @@ create_replication_user() { configure_recovery() { if [[ ! -f ${PG_RECOVERY_CONF} ]]; then - if [[ ${PSQL_MODE} == slave ]]; then + if [[ ${REPLICATION_MODE} == slave ]]; then # initialize recovery.conf on the firstrun (slave only) echo "Configuring recovery..." exec_as_postgres touch ${PG_RECOVERY_CONF} From 761ec9645cc9ad8927683c93feb484f83be676da Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 20:35:13 +0530 Subject: [PATCH 04/14] `PSQL_SSLMODE` config parameter renamed to `REPLICATION_SSLMODE` --- Changelog.md | 1 + entrypoint.sh | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 8462a11..cd36e8b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ - complete rewrite - `PSQL_TRUST_LOCALNET` config parameter renamed to `PG_TRUST_LOCALNET` - `PSQL_MODE` config parameter renamed to `REPLICATION_MODE` +- `PSQL_SSLMODE` config parameter renamed to `REPLICATION_SSLMODE` **9.4-2** - added replication options diff --git a/entrypoint.sh b/entrypoint.sh index 924a235..e2f918d 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,7 +1,6 @@ #!/bin/bash set -e -PSQL_SSLMODE=${PSQL_SSLMODE:-} PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} @@ -12,6 +11,7 @@ REPLICATION_USER=${REPLICATION_USER:-} REPLICATION_PASS=${REPLICATION_PASS:-} REPLICATION_HOST=${REPLICATION_HOST:-} REPLICATION_PORT=${REPLICATION_PORT:-} +REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-} DB_NAME=${DB_NAME:-} DB_USER=${DB_USER:-} @@ -125,7 +125,7 @@ initialize_database() { slave|snapshot) # default params REPLICATION_PORT=${REPLICATION_PORT:-5432} - PSQL_SSLMODE=${PSQL_SSLMODE:-disable} + REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-disable} if [[ -z $REPLICATION_HOST ]]; then echo "ERROR! Cannot continue without the REPLICATION_HOST. Exiting..." @@ -299,7 +299,7 @@ configure_recovery() { echo "Configuring recovery..." exec_as_postgres touch ${PG_RECOVERY_CONF} ( echo "standby_mode = 'on'"; - echo "primary_conninfo = 'host=${REPLICATION_HOST} port=${REPLICATION_PORT} user=${REPLICATION_USER} password=${REPLICATION_PASS} sslmode=${PSQL_SSLMODE}'"; + echo "primary_conninfo = 'host=${REPLICATION_HOST} port=${REPLICATION_PORT} user=${REPLICATION_USER} password=${REPLICATION_PASS} sslmode=${REPLICATION_SSLMODE}'"; echo "trigger_file = '/tmp/postgresql.trigger'" ) > ${PG_RECOVERY_CONF} fi else @@ -307,7 +307,7 @@ configure_recovery() { set_recovery_param "port" "${REPLICATION_PORT}" set_recovery_param "user" "${REPLICATION_USER}" set_recovery_param "password" "${REPLICATION_PASS}" - set_recovery_param "sslmode" "${PSQL_SSLMODE}" + set_recovery_param "sslmode" "${REPLICATION_SSLMODE}" fi } From 7eeda81f1ec319b3e28b003bbcdc649078143777 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 20:35:50 +0530 Subject: [PATCH 05/14] set default value of `REPLICATION_SSLMODE` to `prefer` --- entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index e2f918d..10e9277 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -125,7 +125,7 @@ initialize_database() { slave|snapshot) # default params REPLICATION_PORT=${REPLICATION_PORT:-5432} - REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-disable} + REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-prefer} if [[ -z $REPLICATION_HOST ]]; then echo "ERROR! Cannot continue without the REPLICATION_HOST. Exiting..." From 5f6024062f20c274d3002e7bcc9fc862edb73aa3 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 20:41:47 +0530 Subject: [PATCH 06/14] defined `/etc/postgresql/certs` as the mountpoint to install SSL key and certificate --- Changelog.md | 1 + Dockerfile | 3 ++- entrypoint.sh | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index cd36e8b..79f7a18 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ - `PSQL_TRUST_LOCALNET` config parameter renamed to `PG_TRUST_LOCALNET` - `PSQL_MODE` config parameter renamed to `REPLICATION_MODE` - `PSQL_SSLMODE` config parameter renamed to `REPLICATION_SSLMODE` +- defined `/etc/postgresql/certs` as the mountpoint to install SSL key and certificate **9.4-2** - added replication options diff --git a/Dockerfile b/Dockerfile index ac0cf45..bed4a4d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,8 @@ ENV PG_VERSION=9.4 \ PG_USER=postgres \ PG_HOME=/var/lib/postgresql \ PG_RUNDIR=/run/postgresql \ - PG_LOGDIR=/var/log/postgresql + PG_LOGDIR=/var/log/postgresql \ + PG_CERTDIR=/etc/postgresql/certs ENV PG_BINDIR=/usr/lib/postgresql/${PG_VERSION}/bin \ PG_DATADIR=${PG_HOME}/${PG_VERSION}/main diff --git a/entrypoint.sh b/entrypoint.sh index 10e9277..2172a4e 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -59,6 +59,15 @@ create_datadir() { chown -R ${PG_USER}:${PG_USER} ${PG_HOME} } +create_certdir() { + echo "Initializing certdir..." + mkdir -p ${PG_CERTDIR} + [[ -f ${PG_CERTDIR}/server.crt ]] && chmod 0644 ${PG_CERTDIR}/server.crt + [[ -f ${PG_CERTDIR}/server.key ]] && chmod 0640 ${PG_CERTDIR}/server.key + chmod 0755 ${PG_CERTDIR} + chown -R root:${PG_USER} ${PG_CERTDIR} +} + create_logdir() { echo "Initializing logdir..." mkdir -p ${PG_LOGDIR} @@ -327,6 +336,7 @@ if [[ -z ${1} ]]; then locale_gen create_datadir + create_certdir create_logdir create_rundir From 6418c9e0449b723df21e547cf85c77c3e5996788 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 20:42:55 +0530 Subject: [PATCH 07/14] added `PG_SSL` parameter to enable/disable SSL support `PG_SSL` is automatically set to `on` if `server.crt` and `server.key` can be found at `/etc/postgresql/certs` Closes #32 --- Changelog.md | 1 + entrypoint.sh | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Changelog.md b/Changelog.md index 79f7a18..b3b91a0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ - `PSQL_MODE` config parameter renamed to `REPLICATION_MODE` - `PSQL_SSLMODE` config parameter renamed to `REPLICATION_SSLMODE` - defined `/etc/postgresql/certs` as the mountpoint to install SSL key and certificate +- added `PG_SSL` parameter to enable/disable SSL support **9.4-2** - added replication options diff --git a/entrypoint.sh b/entrypoint.sh index 2172a4e..0e3e7a2 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,6 +1,7 @@ #!/bin/bash set -e +PG_SSL=${PG_SSL:-} PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} @@ -114,6 +115,25 @@ set_hba_param() { fi } +configure_ssl() { + ## NOT SURE IF THIS IS A GOOD ALTERNATIVE TO ENABLE SSL SUPPORT BY DEFAULT ## + ## BECAUSE USERS WHO PULL A PREBUILT IMAGE WILL HAVE THE SAME CERTIFICATES ## + # if [[ ! -f ${PG_CERTDIR}/server.crt && ! -f ${PG_CERTDIR}/server.key ]]; then + # if [[ -f /etc/ssl/certs/ssl-cert-snakeoil.pem && -f /etc/ssl/private/ssl-cert-snakeoil.key ]]; then + # ln -sf /etc/ssl/certs/ssl-cert-snakeoil.pem ${PG_CERTDIR}/server.crt + # ln -sf /etc/ssl/private/ssl-cert-snakeoil.key ${PG_CERTDIR}/server.key + # fi + # fi + + if [[ -f ${PG_CERTDIR}/server.crt && -f ${PG_CERTDIR}/server.key ]]; then + PG_SSL=${PG_SSL:-on} + set_postgresql_param "ssl_cert_file" "${PG_CERTDIR}/server.crt" + set_postgresql_param "ssl_key_file" "${PG_CERTDIR}/server.key" + fi + PG_SSL=${PG_SSL:-off} + set_postgresql_param "ssl" "${PG_SSL}" +} + configure_hot_standby() { case ${REPLICATION_MODE} in slave|snapshot) ;; @@ -341,6 +361,7 @@ if [[ -z ${1} ]]; then create_rundir initialize_database + configure_ssl trust_localnet create_user From faaa87cc64ed78db4e8c352717bb45b2a7bf2918 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 21:02:53 +0530 Subject: [PATCH 08/14] separated out `entrypoint.sh` functions to a `functions` bash module --- Dockerfile | 4 +- entrypoint.sh | 341 +---------------------------------------- runtime/functions | 378 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 382 insertions(+), 341 deletions(-) create mode 100755 runtime/functions diff --git a/Dockerfile b/Dockerfile index bed4a4d..a86229b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,8 @@ FROM sameersbn/ubuntu:14.04.20151117 MAINTAINER sameer@damagehead.com -ENV PG_VERSION=9.4 \ +ENV PG_APP_HOME="/etc/docker-postgresql"\ + PG_VERSION=9.4 \ PG_USER=postgres \ PG_HOME=/var/lib/postgresql \ PG_RUNDIR=/run/postgresql \ @@ -21,6 +22,7 @@ RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-k && rm -rf ${PG_HOME} \ && rm -rf /var/lib/apt/lists/* +COPY runtime/ ${PG_APP_HOME}/ COPY entrypoint.sh /sbin/entrypoint.sh RUN chmod 755 /sbin/entrypoint.sh diff --git a/entrypoint.sh b/entrypoint.sh index 0e3e7a2..f9fff9f 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,344 +1,6 @@ #!/bin/bash set -e - -PG_SSL=${PG_SSL:-} - -PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility -PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} - -REPLICATION_MODE=${REPLICATION_MODE:-$PSQL_MODE} # backward compatibility -REPLICATION_MODE=${REPLICATION_MODE:-} -REPLICATION_USER=${REPLICATION_USER:-} -REPLICATION_PASS=${REPLICATION_PASS:-} -REPLICATION_HOST=${REPLICATION_HOST:-} -REPLICATION_PORT=${REPLICATION_PORT:-} -REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-} - -DB_NAME=${DB_NAME:-} -DB_USER=${DB_USER:-} -DB_PASS=${DB_PASS:-} - -DB_LOCALE=${DB_LOCALE:-C} -DB_UNACCENT=${DB_UNACCENT:-false} - -PG_CONF=${PG_DATADIR}/postgresql.conf -PG_HBA_CONF=${PG_DATADIR}/pg_hba.conf -PG_IDENT_CONF=${PG_DATADIR}/pg_ident.conf -PG_RECOVERY_CONF=${PG_DATADIR}/recovery.conf - -## Execute command as PG_USER -exec_as_postgres() { - sudo -HEu ${PG_USER} "$@" -} - -map_uidgid() { - USERMAP_ORIG_UID=$(id -u ${PG_USER}) - USERMAP_ORIG_GID=$(id -g ${PG_USER}) - USERMAP_GID=${USERMAP_GID:-${USERMAP_UID:-$USERMAP_ORIG_GID}} - USERMAP_UID=${USERMAP_UID:-$USERMAP_ORIG_UID} - if [[ ${USERMAP_UID} != ${USERMAP_ORIG_UID} ]] || [[ ${USERMAP_GID} != ${USERMAP_ORIG_GID} ]]; then - echo "Adapting uid and gid for ${PG_USER}:${PG_USER} to $USERMAP_UID:$USERMAP_GID" - groupmod -g ${USERMAP_GID} ${PG_USER} - sed -i -e "s|:${USERMAP_ORIG_UID}:${USERMAP_GID}:|:${USERMAP_UID}:${USERMAP_GID}:|" /etc/passwd - fi -} - -locale_gen() { - if [[ $DB_LOCALE != C ]]; then - echo "Generating locale \"${DB_LOCALE}\"..." - locale-gen ${DB_LOCALE} >/dev/null - fi -} - -create_datadir() { - echo "Initializing datadir..." - mkdir -p ${PG_HOME} - if [[ -d ${PG_DATADIR} ]]; then - find ${PG_DATADIR} -type f -exec chmod 0600 {} \; - find ${PG_DATADIR} -type d -exec chmod 0700 {} \; - fi - chown -R ${PG_USER}:${PG_USER} ${PG_HOME} -} - -create_certdir() { - echo "Initializing certdir..." - mkdir -p ${PG_CERTDIR} - [[ -f ${PG_CERTDIR}/server.crt ]] && chmod 0644 ${PG_CERTDIR}/server.crt - [[ -f ${PG_CERTDIR}/server.key ]] && chmod 0640 ${PG_CERTDIR}/server.key - chmod 0755 ${PG_CERTDIR} - chown -R root:${PG_USER} ${PG_CERTDIR} -} - -create_logdir() { - echo "Initializing logdir..." - mkdir -p ${PG_LOGDIR} - chmod -R 1775 ${PG_LOGDIR} - chown -R root:${PG_USER} ${PG_LOGDIR} -} - -create_rundir() { - echo "Initializing rundir..." - mkdir -p ${PG_RUNDIR} ${PG_RUNDIR}/${PG_VERSION}-main.pg_stat_tmp - chmod -R 0755 ${PG_RUNDIR} - chmod g+s ${PG_RUNDIR} - chown -R ${PG_USER}:${PG_USER} ${PG_RUNDIR} -} - -set_postgresql_param() { - local key=${1} - local value=${2} - if [[ -n ${value} ]]; then - local current=$(exec_as_postgres sed -n -e "s/^\("${key}" = '\)\([^ ']*\)\(.*\)$/\2/p" ${PG_CONF}) - if [[ "${current}" != "${value}" ]]; then - echo "‣ Setting postgresql.conf parameter: ${key} = '${value}'" - exec_as_postgres sed -i "s|^[#]*[ ]*"${key}" = .*|"${key}" = '"${value}"'|" ${PG_CONF} - fi - fi -} - -set_recovery_param() { - local key=${1} - local value=${2} - if [[ -n ${value} ]]; then - local current=$(exec_as_postgres sed -n -e "s/^\(.*\)\("${key}"=\)\([^ ']*\)\(.*\)$/\3/p" ${PG_RECOVERY_CONF}) - if [[ "${current}" != "${value}" ]]; then - echo "Updating primary_conninfo ${key}..." - exec_as_postgres sed -i "s|"${key}"=[^ ']*|"${key}"="${value}"|" ${PG_RECOVERY_CONF} - fi - fi -} - -set_hba_param() { - local value=${1} - if ! grep -q "$(sed "s| | \\\+|g" <<< ${value})" ${PG_HBA_CONF}; then - echo "${value}" >> ${PG_HBA_CONF} - fi -} - -configure_ssl() { - ## NOT SURE IF THIS IS A GOOD ALTERNATIVE TO ENABLE SSL SUPPORT BY DEFAULT ## - ## BECAUSE USERS WHO PULL A PREBUILT IMAGE WILL HAVE THE SAME CERTIFICATES ## - # if [[ ! -f ${PG_CERTDIR}/server.crt && ! -f ${PG_CERTDIR}/server.key ]]; then - # if [[ -f /etc/ssl/certs/ssl-cert-snakeoil.pem && -f /etc/ssl/private/ssl-cert-snakeoil.key ]]; then - # ln -sf /etc/ssl/certs/ssl-cert-snakeoil.pem ${PG_CERTDIR}/server.crt - # ln -sf /etc/ssl/private/ssl-cert-snakeoil.key ${PG_CERTDIR}/server.key - # fi - # fi - - if [[ -f ${PG_CERTDIR}/server.crt && -f ${PG_CERTDIR}/server.key ]]; then - PG_SSL=${PG_SSL:-on} - set_postgresql_param "ssl_cert_file" "${PG_CERTDIR}/server.crt" - set_postgresql_param "ssl_key_file" "${PG_CERTDIR}/server.key" - fi - PG_SSL=${PG_SSL:-off} - set_postgresql_param "ssl" "${PG_SSL}" -} - -configure_hot_standby() { - case ${REPLICATION_MODE} in - slave|snapshot) ;; - *) - echo "Configuring hot standby..." - set_postgresql_param "wal_level" "hot_standby" - set_postgresql_param "max_wal_senders" "16" - set_postgresql_param "checkpoint_segments" "8" - set_postgresql_param "wal_keep_segments" "32" - set_postgresql_param "hot_standby" "on" - ;; - esac -} - -initialize_database() { - if [[ ! -f ${PG_DATADIR}/PG_VERSION ]]; then - case ${REPLICATION_MODE} in - slave|snapshot) - # default params - REPLICATION_PORT=${REPLICATION_PORT:-5432} - REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-prefer} - - if [[ -z $REPLICATION_HOST ]]; then - echo "ERROR! Cannot continue without the REPLICATION_HOST. Exiting..." - exit 1 - fi - - if [[ -z $REPLICATION_USER ]]; then - echo "ERROR! Cannot continue without the REPLICATION_USER. Exiting..." - exit 1 - fi - - if [[ -z $REPLICATION_PASS ]]; then - echo "ERROR! Cannot continue without the REPLICATION_PASS. Exiting..." - exit 1 - fi - - echo -n "Waiting for $REPLICATION_HOST to accept connections (60s timeout)" - timeout=60 - while ! ${PG_BINDIR}/pg_isready -h $REPLICATION_HOST -p $REPLICATION_PORT -t 1 >/dev/null 2>&1 - do - timeout=$(expr $timeout - 1) - if [[ $timeout -eq 0 ]]; then - echo "Timeout! Exiting..." - exit 1 - fi - echo -n "." - sleep 1 - done - echo - - case ${REPLICATION_MODE} in - slave) - echo "Replicating initial data from $REPLICATION_HOST..." - exec_as_postgres PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ - -h ${REPLICATION_HOST} -p ${REPLICATION_PORT} -U ${REPLICATION_USER} -X stream -w >/dev/null - ;; - snapshot) - echo "Generating a snapshot data on $REPLICATION_HOST..." - exec_as_postgres PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ - -h ${REPLICATION_HOST} -p ${REPLICATION_PORT} -U ${REPLICATION_USER} -X fetch -w >/dev/null - esac - ;; - *) - echo "Initializing database..." - PG_OLD_VERSION=$(find ${PG_HOME}/[0-9].[0-9]/main -maxdepth 1 -name PG_VERSION 2>/dev/null | grep -v $PG_VERSION | sort -r | head -n1 | cut -d'/' -f5) - if [[ -n ${PG_OLD_VERSION} ]]; then - echo "‣ Migrating PostgreSQL ${PG_OLD_VERSION} data to ${PG_VERSION}..." - - # protect the existing data from being altered by apt-get - mv ${PG_HOME}/${PG_OLD_VERSION} ${PG_HOME}/${PG_OLD_VERSION}.migrating - - echo "‣ Installing PostgreSQL ${PG_OLD_VERSION}..." - if ! ( apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-${PG_OLD_VERSION} postgresql-client-${PG_OLD_VERSION} ) >/dev/null; then - echo "ERROR! Failed to install PostgreSQL ${PG_OLD_VERSION}. Exiting..." - # first move the old data back - rm -rf ${PG_HOME}/${PG_OLD_VERSION} - mv ${PG_HOME}/${PG_OLD_VERSION}.migrating ${PG_HOME}/${PG_OLD_VERSION} - exit 1 - fi - rm -rf /var/lib/apt/lists/* - - # we're ready to migrate, move back the old data and remove the trap - rm -rf ${PG_HOME}/${PG_OLD_VERSION} - mv ${PG_HOME}/${PG_OLD_VERSION}.migrating ${PG_HOME}/${PG_OLD_VERSION} - fi - - exec_as_postgres ${PG_BINDIR}/initdb --pgdata=${PG_DATADIR} \ - --username=${PG_USER} --encoding=unicode --locale=${DB_LOCALE} --auth=trust >/dev/null - - if [[ -n ${PG_OLD_VERSION} ]]; then - PG_OLD_BINDIR=/usr/lib/postgresql/${PG_OLD_VERSION}/bin - PG_OLD_DATADIR=${PG_HOME}/${PG_OLD_VERSION}/main - PG_OLD_CONF=${PG_OLD_DATADIR}/postgresql.conf - PG_OLD_HBA_CONF=${PG_OLD_DATADIR}/pg_hba.conf - PG_OLD_IDENT_CONF=${PG_OLD_DATADIR}/pg_ident.conf - - echo -n "‣ Migration in progress. Please be patient..." - exec_as_postgres ${PG_BINDIR}/pg_upgrade \ - -b ${PG_OLD_BINDIR} -B ${PG_BINDIR} \ - -d ${PG_OLD_DATADIR} -D ${PG_DATADIR} \ - -o "-c config_file=${PG_OLD_CONF} --hba_file=${PG_OLD_HBA_CONF} --ident_file=${PG_OLD_IDENT_CONF}" \ - -O "-c config_file=${PG_CONF} --hba_file=${PG_HBA_CONF} --ident_file=${PG_IDENT_CONF}" >/dev/null - echo - fi - ;; - esac - - # configure path to data_directory - set_postgresql_param "data_directory" "${PG_DATADIR}" - - # listen on all interfaces - set_postgresql_param "listen_addresses" "*" - - # allow remote connections to postgresql database - set_hba_param "host all all 0.0.0.0/0 md5" - - configure_hot_standby - - # Change DSM from `posix' to `sysv' if we are inside an lx-brand container - if [[ $(uname -v) == "BrandZ virtual linux" ]]; then - set_postgresql_param "dynamic_shared_memory_type" "sysv" - fi - fi -} - -trust_localnet() { - if [[ ${PG_TRUST_LOCALNET} == true ]]; then - echo "Trusting connections from the local network..." - set_hba_param "host all all samenet trust" - fi -} - -create_user() { - if [[ -n ${DB_USER} ]]; then - if [[ -z ${DB_PASS} ]]; then - echo "ERROR! Please specify a password for DB_USER in DB_PASS. Exiting..." - exit 1 - fi - echo "Creating database user: ${DB_USER}" - echo "CREATE ROLE \"${DB_USER}\" with LOGIN CREATEDB PASSWORD '${DB_PASS}';" | \ - exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 - fi -} - -create_database() { - if [[ -n ${DB_NAME} ]]; then - echo -n "Creating database(s): " - for database in $(awk -F',' '{for (i = 1 ; i <= NF ; i++) print $i}' <<< "${DB_NAME}"); do - echo -n "${database} " - echo "CREATE DATABASE \"${database}\";" | \ - exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 - - if [[ ${DB_UNACCENT} == true ]]; then - echo "CREATE EXTENSION IF NOT EXISTS unaccent;" | \ - exec_as_postgres ${PG_BINDIR}/postgres --single ${database} -D ${PG_DATADIR} >/dev/null 2>&1 - fi - - if [[ -n ${DB_USER} ]]; then - echo "GRANT ALL PRIVILEGES ON DATABASE \"${database}\" to \"${DB_USER}\";" | \ - exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 - fi - done - echo - fi -} - -create_replication_user() { - case $REPLICATION_MODE in - slave|snapshot) ;; # replication user can only be created on the master - *) - if [[ -n ${REPLICATION_USER} ]]; then - if [[ -z ${REPLICATION_PASS} ]]; then - echo "ERROR! Please specify a password for REPLICATION_USER in REPLICATION_PASS. Exiting..." - exit 1 - fi - - echo "Creating replication user: ${REPLICATION_USER}" - echo "CREATE ROLE \"${REPLICATION_USER}\" WITH REPLICATION LOGIN ENCRYPTED PASSWORD '${REPLICATION_PASS}';" | \ - exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 - - set_hba_param "host replication ${REPLICATION_USER} 0.0.0.0/0 md5" - fi - ;; - esac -} - -configure_recovery() { - if [[ ! -f ${PG_RECOVERY_CONF} ]]; then - if [[ ${REPLICATION_MODE} == slave ]]; then - # initialize recovery.conf on the firstrun (slave only) - echo "Configuring recovery..." - exec_as_postgres touch ${PG_RECOVERY_CONF} - ( echo "standby_mode = 'on'"; - echo "primary_conninfo = 'host=${REPLICATION_HOST} port=${REPLICATION_PORT} user=${REPLICATION_USER} password=${REPLICATION_PASS} sslmode=${REPLICATION_SSLMODE}'"; - echo "trigger_file = '/tmp/postgresql.trigger'" ) > ${PG_RECOVERY_CONF} - fi - else - set_recovery_param "host" "${REPLICATION_HOST}" - set_recovery_param "port" "${REPLICATION_PORT}" - set_recovery_param "user" "${REPLICATION_USER}" - set_recovery_param "password" "${REPLICATION_PASS}" - set_recovery_param "sslmode" "${REPLICATION_SSLMODE}" - fi -} +source ${PG_APP_HOME}/functions # allow arguments to be passed to postgres if [[ ${1:0:1} = '-' ]]; then @@ -351,7 +13,6 @@ fi # default behaviour is to launch postgres if [[ -z ${1} ]]; then - map_uidgid locale_gen diff --git a/runtime/functions b/runtime/functions new file mode 100755 index 0000000..bc79fd1 --- /dev/null +++ b/runtime/functions @@ -0,0 +1,378 @@ +#!/bin/bash +set -e + +PG_SSL=${PG_SSL:-} + +PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility +PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} + +REPLICATION_MODE=${REPLICATION_MODE:-$PSQL_MODE} # backward compatibility +REPLICATION_MODE=${REPLICATION_MODE:-} +REPLICATION_USER=${REPLICATION_USER:-} +REPLICATION_PASS=${REPLICATION_PASS:-} +REPLICATION_HOST=${REPLICATION_HOST:-} +REPLICATION_PORT=${REPLICATION_PORT:-} +REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-} + +DB_NAME=${DB_NAME:-} +DB_USER=${DB_USER:-} +DB_PASS=${DB_PASS:-} + +DB_LOCALE=${DB_LOCALE:-C} +DB_UNACCENT=${DB_UNACCENT:-false} + +PG_CONF=${PG_DATADIR}/postgresql.conf +PG_HBA_CONF=${PG_DATADIR}/pg_hba.conf +PG_IDENT_CONF=${PG_DATADIR}/pg_ident.conf +PG_RECOVERY_CONF=${PG_DATADIR}/recovery.conf + +## Execute command as PG_USER +exec_as_postgres() { + sudo -HEu ${PG_USER} "$@" +} + +map_uidgid() { + USERMAP_ORIG_UID=$(id -u ${PG_USER}) + USERMAP_ORIG_GID=$(id -g ${PG_USER}) + USERMAP_GID=${USERMAP_GID:-${USERMAP_UID:-$USERMAP_ORIG_GID}} + USERMAP_UID=${USERMAP_UID:-$USERMAP_ORIG_UID} + if [[ ${USERMAP_UID} != ${USERMAP_ORIG_UID} ]] || [[ ${USERMAP_GID} != ${USERMAP_ORIG_GID} ]]; then + echo "Adapting uid and gid for ${PG_USER}:${PG_USER} to $USERMAP_UID:$USERMAP_GID" + groupmod -g ${USERMAP_GID} ${PG_USER} + sed -i -e "s|:${USERMAP_ORIG_UID}:${USERMAP_GID}:|:${USERMAP_UID}:${USERMAP_GID}:|" /etc/passwd + fi +} + +locale_gen() { + if [[ $DB_LOCALE != C ]]; then + echo "Generating locale \"${DB_LOCALE}\"..." + locale-gen ${DB_LOCALE} >/dev/null + fi +} + +create_datadir() { + echo "Initializing datadir..." + mkdir -p ${PG_HOME} + if [[ -d ${PG_DATADIR} ]]; then + find ${PG_DATADIR} -type f -exec chmod 0600 {} \; + find ${PG_DATADIR} -type d -exec chmod 0700 {} \; + fi + chown -R ${PG_USER}:${PG_USER} ${PG_HOME} +} + +create_certdir() { + echo "Initializing certdir..." + mkdir -p ${PG_CERTDIR} + [[ -f ${PG_CERTDIR}/server.crt ]] && chmod 0644 ${PG_CERTDIR}/server.crt + [[ -f ${PG_CERTDIR}/server.key ]] && chmod 0640 ${PG_CERTDIR}/server.key + chmod 0755 ${PG_CERTDIR} + chown -R root:${PG_USER} ${PG_CERTDIR} +} + +create_logdir() { + echo "Initializing logdir..." + mkdir -p ${PG_LOGDIR} + chmod -R 1775 ${PG_LOGDIR} + chown -R root:${PG_USER} ${PG_LOGDIR} +} + +create_rundir() { + echo "Initializing rundir..." + mkdir -p ${PG_RUNDIR} ${PG_RUNDIR}/${PG_VERSION}-main.pg_stat_tmp + chmod -R 0755 ${PG_RUNDIR} + chmod g+s ${PG_RUNDIR} + chown -R ${PG_USER}:${PG_USER} ${PG_RUNDIR} +} + +set_postgresql_param() { + local key=${1} + local value=${2} + if [[ -n ${value} ]]; then + local current=$(exec_as_postgres sed -n -e "s/^\("${key}" = '\)\([^ ']*\)\(.*\)$/\2/p" ${PG_CONF}) + if [[ "${current}" != "${value}" ]]; then + echo "‣ Setting postgresql.conf parameter: ${key} = '${value}'" + exec_as_postgres sed -i "s|^[#]*[ ]*"${key}" = .*|"${key}" = '"${value}"'|" ${PG_CONF} + fi + fi +} + +set_recovery_param() { + local key=${1} + local value=${2} + if [[ -n ${value} ]]; then + local current=$(exec_as_postgres sed -n -e "s/^\(.*\)\("${key}"=\)\([^ ']*\)\(.*\)$/\3/p" ${PG_RECOVERY_CONF}) + if [[ "${current}" != "${value}" ]]; then + echo "Updating primary_conninfo ${key}..." + exec_as_postgres sed -i "s|"${key}"=[^ ']*|"${key}"="${value}"|" ${PG_RECOVERY_CONF} + fi + fi +} + +set_hba_param() { + local value=${1} + if ! grep -q "$(sed "s| | \\\+|g" <<< ${value})" ${PG_HBA_CONF}; then + echo "${value}" >> ${PG_HBA_CONF} + fi +} + +configure_ssl() { + ## NOT SURE IF THIS IS A GOOD ALTERNATIVE TO ENABLE SSL SUPPORT BY DEFAULT ## + ## BECAUSE USERS WHO PULL A PREBUILT IMAGE WILL HAVE THE SAME CERTIFICATES ## + # if [[ ! -f ${PG_CERTDIR}/server.crt && ! -f ${PG_CERTDIR}/server.key ]]; then + # if [[ -f /etc/ssl/certs/ssl-cert-snakeoil.pem && -f /etc/ssl/private/ssl-cert-snakeoil.key ]]; then + # ln -sf /etc/ssl/certs/ssl-cert-snakeoil.pem ${PG_CERTDIR}/server.crt + # ln -sf /etc/ssl/private/ssl-cert-snakeoil.key ${PG_CERTDIR}/server.key + # fi + # fi + + if [[ -f ${PG_CERTDIR}/server.crt && -f ${PG_CERTDIR}/server.key ]]; then + PG_SSL=${PG_SSL:-on} + set_postgresql_param "ssl_cert_file" "${PG_CERTDIR}/server.crt" + set_postgresql_param "ssl_key_file" "${PG_CERTDIR}/server.key" + fi + PG_SSL=${PG_SSL:-off} + set_postgresql_param "ssl" "${PG_SSL}" +} + +configure_hot_standby() { + case ${REPLICATION_MODE} in + slave|snapshot) ;; + *) + echo "Configuring hot standby..." + set_postgresql_param "wal_level" "hot_standby" + set_postgresql_param "max_wal_senders" "16" + set_postgresql_param "checkpoint_segments" "8" + set_postgresql_param "wal_keep_segments" "32" + set_postgresql_param "hot_standby" "on" + ;; + esac +} + +initialize_database() { + if [[ ! -f ${PG_DATADIR}/PG_VERSION ]]; then + case ${REPLICATION_MODE} in + slave|snapshot) + # default params + REPLICATION_PORT=${REPLICATION_PORT:-5432} + REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-prefer} + + if [[ -z $REPLICATION_HOST ]]; then + echo "ERROR! Cannot continue without the REPLICATION_HOST. Exiting..." + exit 1 + fi + + if [[ -z $REPLICATION_USER ]]; then + echo "ERROR! Cannot continue without the REPLICATION_USER. Exiting..." + exit 1 + fi + + if [[ -z $REPLICATION_PASS ]]; then + echo "ERROR! Cannot continue without the REPLICATION_PASS. Exiting..." + exit 1 + fi + + echo -n "Waiting for $REPLICATION_HOST to accept connections (60s timeout)" + timeout=60 + while ! ${PG_BINDIR}/pg_isready -h $REPLICATION_HOST -p $REPLICATION_PORT -t 1 >/dev/null 2>&1 + do + timeout=$(expr $timeout - 1) + if [[ $timeout -eq 0 ]]; then + echo "Timeout! Exiting..." + exit 1 + fi + echo -n "." + sleep 1 + done + echo + + case ${REPLICATION_MODE} in + slave) + echo "Replicating initial data from $REPLICATION_HOST..." + exec_as_postgres PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ + -h ${REPLICATION_HOST} -p ${REPLICATION_PORT} -U ${REPLICATION_USER} -X stream -w >/dev/null + ;; + snapshot) + echo "Generating a snapshot data on $REPLICATION_HOST..." + exec_as_postgres PGPASSWORD=$REPLICATION_PASS ${PG_BINDIR}/pg_basebackup -D ${PG_DATADIR} \ + -h ${REPLICATION_HOST} -p ${REPLICATION_PORT} -U ${REPLICATION_USER} -X fetch -w >/dev/null + esac + ;; + *) + echo "Initializing database..." + PG_OLD_VERSION=$(find ${PG_HOME}/[0-9].[0-9]/main -maxdepth 1 -name PG_VERSION 2>/dev/null | grep -v $PG_VERSION | sort -r | head -n1 | cut -d'/' -f5) + if [[ -n ${PG_OLD_VERSION} ]]; then + echo "‣ Migrating PostgreSQL ${PG_OLD_VERSION} data to ${PG_VERSION}..." + + # protect the existing data from being altered by apt-get + mv ${PG_HOME}/${PG_OLD_VERSION} ${PG_HOME}/${PG_OLD_VERSION}.migrating + + echo "‣ Installing PostgreSQL ${PG_OLD_VERSION}..." + if ! ( apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-${PG_OLD_VERSION} postgresql-client-${PG_OLD_VERSION} ) >/dev/null; then + echo "ERROR! Failed to install PostgreSQL ${PG_OLD_VERSION}. Exiting..." + # first move the old data back + rm -rf ${PG_HOME}/${PG_OLD_VERSION} + mv ${PG_HOME}/${PG_OLD_VERSION}.migrating ${PG_HOME}/${PG_OLD_VERSION} + exit 1 + fi + rm -rf /var/lib/apt/lists/* + + # we're ready to migrate, move back the old data and remove the trap + rm -rf ${PG_HOME}/${PG_OLD_VERSION} + mv ${PG_HOME}/${PG_OLD_VERSION}.migrating ${PG_HOME}/${PG_OLD_VERSION} + fi + + exec_as_postgres ${PG_BINDIR}/initdb --pgdata=${PG_DATADIR} \ + --username=${PG_USER} --encoding=unicode --locale=${DB_LOCALE} --auth=trust >/dev/null + + if [[ -n ${PG_OLD_VERSION} ]]; then + PG_OLD_BINDIR=/usr/lib/postgresql/${PG_OLD_VERSION}/bin + PG_OLD_DATADIR=${PG_HOME}/${PG_OLD_VERSION}/main + PG_OLD_CONF=${PG_OLD_DATADIR}/postgresql.conf + PG_OLD_HBA_CONF=${PG_OLD_DATADIR}/pg_hba.conf + PG_OLD_IDENT_CONF=${PG_OLD_DATADIR}/pg_ident.conf + + echo -n "‣ Migration in progress. Please be patient..." + exec_as_postgres ${PG_BINDIR}/pg_upgrade \ + -b ${PG_OLD_BINDIR} -B ${PG_BINDIR} \ + -d ${PG_OLD_DATADIR} -D ${PG_DATADIR} \ + -o "-c config_file=${PG_OLD_CONF} --hba_file=${PG_OLD_HBA_CONF} --ident_file=${PG_OLD_IDENT_CONF}" \ + -O "-c config_file=${PG_CONF} --hba_file=${PG_HBA_CONF} --ident_file=${PG_IDENT_CONF}" >/dev/null + echo + fi + ;; + esac + + # configure path to data_directory + set_postgresql_param "data_directory" "${PG_DATADIR}" + + # listen on all interfaces + set_postgresql_param "listen_addresses" "*" + + # allow remote connections to postgresql database + set_hba_param "host all all 0.0.0.0/0 md5" + + configure_hot_standby + + # Change DSM from `posix' to `sysv' if we are inside an lx-brand container + if [[ $(uname -v) == "BrandZ virtual linux" ]]; then + set_postgresql_param "dynamic_shared_memory_type" "sysv" + fi + fi +} + +trust_localnet() { + if [[ ${PG_TRUST_LOCALNET} == true ]]; then + echo "Trusting connections from the local network..." + set_hba_param "host all all samenet trust" + fi +} + +create_user() { + if [[ -n ${DB_USER} ]]; then + if [[ -z ${DB_PASS} ]]; then + echo "ERROR! Please specify a password for DB_USER in DB_PASS. Exiting..." + exit 1 + fi + echo "Creating database user: ${DB_USER}" + echo "CREATE ROLE \"${DB_USER}\" with LOGIN CREATEDB PASSWORD '${DB_PASS}';" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 + fi +} + +create_database() { + if [[ -n ${DB_NAME} ]]; then + echo -n "Creating database(s): " + for database in $(awk -F',' '{for (i = 1 ; i <= NF ; i++) print $i}' <<< "${DB_NAME}"); do + echo -n "${database} " + echo "CREATE DATABASE \"${database}\";" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 + + if [[ ${DB_UNACCENT} == true ]]; then + echo "CREATE EXTENSION IF NOT EXISTS unaccent;" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single ${database} -D ${PG_DATADIR} >/dev/null 2>&1 + fi + + if [[ -n ${DB_USER} ]]; then + echo "GRANT ALL PRIVILEGES ON DATABASE \"${database}\" to \"${DB_USER}\";" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 + fi + done + echo + fi +} + +create_replication_user() { + case $REPLICATION_MODE in + slave|snapshot) ;; # replication user can only be created on the master + *) + if [[ -n ${REPLICATION_USER} ]]; then + if [[ -z ${REPLICATION_PASS} ]]; then + echo "ERROR! Please specify a password for REPLICATION_USER in REPLICATION_PASS. Exiting..." + exit 1 + fi + + echo "Creating replication user: ${REPLICATION_USER}" + echo "CREATE ROLE \"${REPLICATION_USER}\" WITH REPLICATION LOGIN ENCRYPTED PASSWORD '${REPLICATION_PASS}';" | \ + exec_as_postgres ${PG_BINDIR}/postgres --single -D ${PG_DATADIR} >/dev/null 2>&1 + + set_hba_param "host replication ${REPLICATION_USER} 0.0.0.0/0 md5" + fi + ;; + esac +} + +configure_recovery() { + if [[ ! -f ${PG_RECOVERY_CONF} ]]; then + if [[ ${REPLICATION_MODE} == slave ]]; then + # initialize recovery.conf on the firstrun (slave only) + echo "Configuring recovery..." + exec_as_postgres touch ${PG_RECOVERY_CONF} + ( echo "standby_mode = 'on'"; + echo "primary_conninfo = 'host=${REPLICATION_HOST} port=${REPLICATION_PORT} user=${REPLICATION_USER} password=${REPLICATION_PASS} sslmode=${REPLICATION_SSLMODE}'"; + echo "trigger_file = '/tmp/postgresql.trigger'" ) > ${PG_RECOVERY_CONF} + fi + else + set_recovery_param "host" "${REPLICATION_HOST}" + set_recovery_param "port" "${REPLICATION_PORT}" + set_recovery_param "user" "${REPLICATION_USER}" + set_recovery_param "password" "${REPLICATION_PASS}" + set_recovery_param "sslmode" "${REPLICATION_SSLMODE}" + fi +} + +# allow arguments to be passed to postgers +if [[ ${1:0:1} = '-' ]]; then + EXTRA_ARGS="$@" + set -- +elif [[ ${1} == mongod || ${1} == $(which mongod) ]]; then + EXTRA_ARGS="${@:2}" + set -- +fi + +# default behaviour is to launch postgres +if [[ -z ${1} ]]; then + + map_uidgid + locale_gen + + create_datadir + create_certdir + create_logdir + create_rundir + + initialize_database + configure_ssl + trust_localnet + + create_user + create_database + create_replication_user + configure_recovery + + echo "Starting PostgreSQL ${PG_VERSION}..." + exec start-stop-daemon --start --chuid ${PG_USER}:${PG_USER} \ + --exec ${PG_BINDIR}/postgres -- -D ${PG_DATADIR} ${EXTRA_ARGS} +else + exec "$@" +fi + From 978c70a78a2eda74e2276fe34e21fcce7b07a17a Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 21:06:13 +0530 Subject: [PATCH 09/14] moved default script variables to `env-defaults` --- runtime/env-defaults | 21 +++++++++++++++++++++ runtime/functions | 21 +-------------------- 2 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 runtime/env-defaults diff --git a/runtime/env-defaults b/runtime/env-defaults new file mode 100644 index 0000000..4b1eb4c --- /dev/null +++ b/runtime/env-defaults @@ -0,0 +1,21 @@ +#!/bin/bash + +PG_SSL=${PG_SSL:-} + +PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility +PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} + +REPLICATION_MODE=${REPLICATION_MODE:-$PSQL_MODE} # backward compatibility +REPLICATION_MODE=${REPLICATION_MODE:-} +REPLICATION_USER=${REPLICATION_USER:-} +REPLICATION_PASS=${REPLICATION_PASS:-} +REPLICATION_HOST=${REPLICATION_HOST:-} +REPLICATION_PORT=${REPLICATION_PORT:-} +REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-} + +DB_NAME=${DB_NAME:-} +DB_USER=${DB_USER:-} +DB_PASS=${DB_PASS:-} + +DB_LOCALE=${DB_LOCALE:-C} +DB_UNACCENT=${DB_UNACCENT:-false} diff --git a/runtime/functions b/runtime/functions index bc79fd1..ced1a2f 100755 --- a/runtime/functions +++ b/runtime/functions @@ -1,25 +1,6 @@ #!/bin/bash set -e - -PG_SSL=${PG_SSL:-} - -PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility -PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} - -REPLICATION_MODE=${REPLICATION_MODE:-$PSQL_MODE} # backward compatibility -REPLICATION_MODE=${REPLICATION_MODE:-} -REPLICATION_USER=${REPLICATION_USER:-} -REPLICATION_PASS=${REPLICATION_PASS:-} -REPLICATION_HOST=${REPLICATION_HOST:-} -REPLICATION_PORT=${REPLICATION_PORT:-} -REPLICATION_SSLMODE=${REPLICATION_SSLMODE:-} - -DB_NAME=${DB_NAME:-} -DB_USER=${DB_USER:-} -DB_PASS=${DB_PASS:-} - -DB_LOCALE=${DB_LOCALE:-C} -DB_UNACCENT=${DB_UNACCENT:-false} +source ${PG_APP_HOME}/env-defaults PG_CONF=${PG_DATADIR}/postgresql.conf PG_HBA_CONF=${PG_DATADIR}/pg_hba.conf From 7f1e051f1a99c3b5c8917c673cd40c0743fea38e Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Thu, 26 Nov 2015 22:29:20 +0530 Subject: [PATCH 10/14] remove extra whitespace --- runtime/functions | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/functions b/runtime/functions index ced1a2f..f12413c 100755 --- a/runtime/functions +++ b/runtime/functions @@ -332,7 +332,6 @@ fi # default behaviour is to launch postgres if [[ -z ${1} ]]; then - map_uidgid locale_gen From afe467bec7841180127d086fa7a67b09842d0304 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Fri, 27 Nov 2015 10:02:48 +0530 Subject: [PATCH 11/14] added sample `docker-compose.yml` --- docker-compose.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e80c9f7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +PostgreSQL: + restart: always + image: sameersbn/postgresql:9.4-8 + ports: + - "5432:5432" + environment: + - DB_USER= + - DB_PASS= + - DB_NAME= + - REPLICATION_MODE= + - REPLICATION_USER= + - REPLICATION_PASS= + - REPLICATION_SSLMODE= + volumes: + - /srv/docker/postgresql:/var/lib/postgresql From 5e09110558dfa41ff9c3f42314b40699a1888cd5 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Fri, 27 Nov 2015 11:04:48 +0530 Subject: [PATCH 12/14] configure logging options on first run --- runtime/functions | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runtime/functions b/runtime/functions index f12413c..c071280 100755 --- a/runtime/functions +++ b/runtime/functions @@ -226,6 +226,10 @@ initialize_database() { # configure path to data_directory set_postgresql_param "data_directory" "${PG_DATADIR}" + # configure logging + set_postgresql_param "log_directory" "${PG_LOGDIR}" + set_postgresql_param "log_filename" "postgresql-${PG_VERSION}-main.log" + # listen on all interfaces set_postgresql_param "listen_addresses" "*" From ce2caf39379920530dcae69d2e0bad4527675b20 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Fri, 27 Nov 2015 14:47:04 +0530 Subject: [PATCH 13/14] `DB_LOCALE` config parameter renamed to `PG_LOCALE` --- Changelog.md | 1 + README.md | 2 +- runtime/env-defaults | 4 +++- runtime/functions | 8 ++++---- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index b3b91a0..9905c29 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ - `PSQL_SSLMODE` config parameter renamed to `REPLICATION_SSLMODE` - defined `/etc/postgresql/certs` as the mountpoint to install SSL key and certificate - added `PG_SSL` parameter to enable/disable SSL support +- `DB_LOCALE` config parameter renamed to `PG_LOCALE` **9.4-2** - added replication options diff --git a/README.md b/README.md index 294d867..b88004b 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ docker run --name postgresql -d \ will create a user *dbuser* with the password *dbpass*. It will also create a database named *dbname* and the *dbuser* user will have full access to the *dbname* database. -The `DB_LOCALE` environment variable can be used to configure the locale used for database creation. Its default value is set to C. +The `PG_LOCALE` environment variable can be used to configure the locale used for database creation. Its default value is set to C. The `PG_TRUST_LOCALNET` environment variable can be used to configure postgres to trust connections on the same network. This is handy for other containers to connect without authentication. To enable this behavior, set `PG_TRUST_LOCALNET` to `true`. diff --git a/runtime/env-defaults b/runtime/env-defaults index 4b1eb4c..3cf29fa 100644 --- a/runtime/env-defaults +++ b/runtime/env-defaults @@ -2,6 +2,9 @@ PG_SSL=${PG_SSL:-} +PG_LOCALE=${PG_LOCALE:-$DB_LOCALE} # backward compatibility +PG_LOCALE=${PG_LOCALE:-C} + PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-$PSQL_TRUST_LOCALNET} # backward compatibility PG_TRUST_LOCALNET=${PG_TRUST_LOCALNET:-false} @@ -17,5 +20,4 @@ DB_NAME=${DB_NAME:-} DB_USER=${DB_USER:-} DB_PASS=${DB_PASS:-} -DB_LOCALE=${DB_LOCALE:-C} DB_UNACCENT=${DB_UNACCENT:-false} diff --git a/runtime/functions b/runtime/functions index c071280..42b8157 100755 --- a/runtime/functions +++ b/runtime/functions @@ -25,9 +25,9 @@ map_uidgid() { } locale_gen() { - if [[ $DB_LOCALE != C ]]; then - echo "Generating locale \"${DB_LOCALE}\"..." - locale-gen ${DB_LOCALE} >/dev/null + if [[ $PG_LOCALE != C ]]; then + echo "Generating locale \"${PG_LOCALE}\"..." + locale-gen ${PG_LOCALE} >/dev/null fi } @@ -203,7 +203,7 @@ initialize_database() { fi exec_as_postgres ${PG_BINDIR}/initdb --pgdata=${PG_DATADIR} \ - --username=${PG_USER} --encoding=unicode --locale=${DB_LOCALE} --auth=trust >/dev/null + --username=${PG_USER} --encoding=unicode --locale=${PG_LOCALE} --auth=trust >/dev/null if [[ -n ${PG_OLD_VERSION} ]]; then PG_OLD_BINDIR=/usr/lib/postgresql/${PG_OLD_VERSION}/bin From c5522c9fda7250c553ef5290890c3b9f2fbe97f9 Mon Sep 17 00:00:00 2001 From: Sameer Naik Date: Fri, 27 Nov 2015 15:10:34 +0530 Subject: [PATCH 14/14] complete rewrite of the README --- Changelog.md | 1 + README.md | 377 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 234 insertions(+), 144 deletions(-) diff --git a/Changelog.md b/Changelog.md index 9905c29..942e0ae 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ - defined `/etc/postgresql/certs` as the mountpoint to install SSL key and certificate - added `PG_SSL` parameter to enable/disable SSL support - `DB_LOCALE` config parameter renamed to `PG_LOCALE` +- complete rewrite of the README **9.4-2** - added replication options diff --git a/README.md b/README.md index b88004b..323c1e1 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,59 @@ -[![Circle CI](https://circleci.com/gh/sameersbn/docker-postgresql.svg?style=svg)](https://circleci.com/gh/sameersbn/docker-postgresql) [![Docker Repository on Quay.io](https://quay.io/repository/sameersbn/postgresql/status "Docker Repository on Quay.io")](https://quay.io/repository/sameersbn/postgresql) +[![Circle CI](https://circleci.com/gh/sameersbn/docker-postgresql.svg?style=shield)](https://circleci.com/gh/sameersbn/docker-postgresql) [![Docker Repository on Quay.io](https://quay.io/repository/sameersbn/postgresql/status "Docker Repository on Quay.io")](https://quay.io/repository/sameersbn/postgresql) -# Table of Contents +# sameersbn/postgresql:9.4-8 - [Introduction](#introduction) -- [Changelog](Changelog.md) -- [Contributing](#contributing) -- [Reporting Issues](#reporting-issues) -- [Installation](#installation) -- [Quick Start](#quick-start) -- [Persistence](#persistence) -- [Creating User and Database at Launch](#creating-user-and-database-at-launch) -- [Creating a Snapshot or Slave Database](#creating-a-snapshot-or-slave-database) -- [Host UID / GID Mapping](#host-uid--gid-mapping) -- [Upgrading](#upgrading) -- [Shell Access](#shell-access) + - [Contributing](#contributing) + - [Issues](#issues) +- [Getting started](#getting-started) + - [Installation](#installation) + - [Quickstart](#quickstart) + - [Command-line arguments](#command-line-arguments) + - [Persistence](#persistence) + - [Setting the default locale](#setting-the-default-locale) + - [Trusting local connections](#trusting-local-connections) + - [Creating database user](#creating-database-user) + - [Creating databases](#creating-databases) + - [Enabling unaccent extension](#enabling-unaccent-extension) + - [Granting user access to a database](#granting-user-access-to-a-database) + - [Creating replication user](#creating-replication-user) + - [Setting up a replication cluster](#setting-up-a-replication-cluster) + - [Creating a snapshot](#creating-a-snapshot) + - [Logs](#logs) + - [UID/GID mapping](#uid-gid-mapping) +- [Maintenance](#maintenance) + - [Upgrading](#upgrading) + - [Shell Access](#shell-access) # Introduction -Dockerfile to build a PostgreSQL container image which can be linked to other containers. +`Dockerfile` to create a [Docker](https://www.docker.com/) container image for [PostgreSQL](http://postgresql.org/). -# Contributing +PostgreSQL is an object-relational database management system (ORDBMS) with an emphasis on extensibility and standards-compliance [[source](https://en.wikipedia.org/wiki/PostgreSQL)]. + +## Contributing If you find this image useful here's how you can help: -- Send a Pull Request with your awesome new features and bug fixes -- Help new users with [Issues](https://github.com/sameersbn/docker-postgresql/issues) they may encounter +- Send a pull request with your awesome features and bug fixes +- Help users resolve their [issues](../../issues?q=is%3Aopen+is%3Aissue). - Support the development of this image with a [donation](http://www.damagehead.com/donate/) -# Reporting Issues +## Issues -Docker is a relatively new project and is being actively developed and tested by a thriving community of developers and testers and every release of Docker features many enhancements and bugfixes. +Before reporting your issue please try updating Docker to the latest version and check if it resolves the issue. Refer to the Docker [installation guide](https://docs.docker.com/installation) for instructions. -Given the nature of the development and release cycle it is very important that you have the latest version of docker installed because any issue that you encounter might have already been fixed with a newer docker release. +SELinux users should try disabling SELinux using the command `setenforce 0` to see if it resolves the issue. -For ubuntu users I suggest [installing docker](https://docs.docker.com/installation/ubuntulinux/) using docker's own package repository since the version of docker packaged in the ubuntu repositories are a little dated. +If the above recommendations do not help then [report your issue](../../issues/new) along with the following information: -Here is the shortform of the installation of an updated version of docker on ubuntu. +- Output of the `docker version` and `docker info` commands +- The `docker run` command or `docker-compose.yml` used to start the image. Mask out the sensitive bits. +- Please state if you are using [Boot2Docker](http://www.boot2docker.io), [VirtualBox](https://www.virtualbox.org), etc. -```bash -sudo apt-get purge docker.io -curl -s https://get.docker.io/ubuntu/ | sudo sh -sudo apt-get update -sudo apt-get install lxc-docker -``` +# Getting started -Fedora and RHEL/CentOS users should try disabling selinux with `setenforce 0` and check if resolves the issue. If it does than there is not much that I can help you with. You can either stick with selinux disabled (not recommended by redhat) or switch to using ubuntu. - -If using the latest docker version and/or disabling selinux does not fix the issue then please file a issue request on the [issues](https://github.com/sameersbn/docker-postgresql/issues) page. - -In your issue report please make sure you provide the following information: - -- The host distribution and release version. -- Output of the `docker version` command -- Output of the `docker info` command -- The `docker run` command you used to run the image (mask out the sensitive bits). - -# Installation +## Installation Automated builds of the image are available on [Dockerhub](https://hub.docker.com/r/sameersbn/postgresql) and is the recommended method of installation. @@ -65,203 +63,294 @@ Automated builds of the image are available on [Dockerhub](https://hub.docker.co docker pull sameersbn/postgresql:9.4-8 ``` -Alternately you can build the image yourself. +Alternatively you can build the image yourself. ```bash docker build -t sameersbn/postgresql github.com/sameersbn/docker-postgresql ``` -# Quick Start +## Quickstart -Run the postgresql image +Start PostgreSQL using: ```bash -docker run --name postgresql -d sameersbn/postgresql:9.4-8 +docker run --name postgresql -itd --restart always \ + --publish 5432:5432 \ + --volume /srv/docker/postgresql:/var/lib/postgresql \ + sameersbn/postgresql:9.4-8 ``` -The simplest way to login to the postgresql container as the administrative `postgres` user is to use the `docker exec` command to attach a new process to the running container and connect to the postgresql server over the unix socket. +Login to the PostgreSQL server using: ```bash docker exec -it postgresql sudo -u postgres psql ``` -# Persistence +*Alternatively, you can use the sample [docker-compose.yml](docker-compose.yml) file to start the container using [Docker Compose](https://docs.docker.com/compose/)* -For data persistence a volume should be mounted at `/var/lib/postgresql`. +## Command-line arguments -SELinux users are also required to change the security context of the mount point so that it plays nicely with selinux. +You can customize the launch command of PostgreSQL server by specifying arguments for `postgres` on the `docker run` command. For example the following command enables connection logging: ```bash -mkdir -p /opt/postgresql/data -sudo chcon -Rt svirt_sandbox_file_t /opt/postgresql/data +docker run --name postgresql -itd --restart always \ + sameersbn/postgresql:9.4-8 -c log_connections=on ``` -The updated run command looks like this. +Please refer to the documentation of [postgres](http://www.postgresql.org/docs/9.4/static/app-postgres.html) for the complete list of available options. + +## Persistence + +For PostgreSQL to preserve its state across container shutdown and startup you should mount a volume at `/var/lib/postgresql`. + +> *The [Quickstart](#quickstart) command already mounts a volume for persistence.* + +SELinux users should update the security context of the host mountpoint so that it plays nicely with Docker: ```bash -docker run --name postgresql -d \ - -v /opt/postgresql/data:/var/lib/postgresql sameersbn/postgresql:9.4-8 +mkdir -p /srv/docker/postgresql +chcon -Rt svirt_sandbox_file_t /srv/docker/postgresql ``` -This will make sure that the data stored in the database is not lost when the image is stopped and started again. +## Setting the default locale -# Creating User and Database at Launch +*This is an experimental option. Please share your feedback and/or suggestions* -The image allows you to create a user and database at launch time. +Using the `PG_LOCALE` variable you can set the default [locale](http://www.postgresql.org/docs/9.4/static/locale.html) for the database cluster. The default value of this variable is `PG_LOCALE=C`. -To create a new user you should specify the `DB_USER` and `DB_PASS` variables. The following command will create a new user *dbuser* with the password *dbpass*. +[Example redacted] + +> **Note** +> +> This variable is effective only on the first run of the container as it is used while creating a new PostgreSQL database cluster using [initdb](http://www.postgresql.org/docs/9.4/static/app-initdb.html) + +## Trusting local connections + +By default connections to the PostgreSQL server need to authenticated using a password. If desired you can trust connections from the local network using the `PG_TRUST_LOCALNET` variable. ```bash -docker run --name postgresql -d \ - -e 'DB_USER=dbuser' -e 'DB_PASS=dbpass' \ +docker run --name postgresql -itd --restart always \ + --env 'PG_TRUST_LOCALNET=true' \ sameersbn/postgresql:9.4-8 ``` -**NOTE** -- If the password is not specified the user will not be created -- If the user user already exists no changes will be made +> **Note** +> +> The local network here is network to which the container is attached. This has different meanings depending on the `--net` parameter specified while starting the container. In the default configuration, this parameter would trust connections from other containers on the `docker0` bridge. -Similarly, you can also create a new database by specifying the database name in the `DB_NAME` variable. +## Creating database user + +A new PostgreSQL database user can be created by specifying the `DB_USER` and `DB_PASS` variables while starting the container. ```bash -docker run --name postgresql -d \ - -e 'DB_NAME=dbname' sameersbn/postgresql:9.4-8 -``` - -You may also specify a comma separated list of database names in the `DB_NAME` variable. The following command creates two new databases named *dbname1* and *dbname2* (p.s. this feature is only available in releases greater than 9.1-1). - -```bash -docker run --name postgresql -d \ - -e 'DB_NAME=dbname1,dbname2' \ +docker run --name postgresql -itd --restart always \ + --env 'DB_USER=dbuser' --env 'DB_PASS=dbuserpass' \ sameersbn/postgresql:9.4-8 ``` -If the `DB_USER` and `DB_PASS` variables are also specified while creating the database, then the user is granted access to the database(s). +> **Notes** +> +> - The created user can login remotely +> - The container will error out if a password is not specified for the user +> - No changes will be made if the user already exists +> - Only a single user can be created at each launch -For example, +## Creating databases + +A new PostgreSQL database can be created by specifying the `DB_NAME` variable while starting the container. ```bash -docker run --name postgresql -d \ - -e 'DB_USER=dbuser' -e 'DB_PASS=dbpass' -e 'DB_NAME=dbname' \ +docker run --name postgresql -itd --restart always \ + --env 'DB_NAME=dbname' \ sameersbn/postgresql:9.4-8 ``` -will create a user *dbuser* with the password *dbpass*. It will also create a database named *dbname* and the *dbuser* user will have full access to the *dbname* database. +Additionally, more than one database can be created by specifying a comma separated list of database names in `DB_NAME`. For example, the following command creates two new databases named `dbname1` and `dbname2`. -The `PG_LOCALE` environment variable can be used to configure the locale used for database creation. Its default value is set to C. - -The `PG_TRUST_LOCALNET` environment variable can be used to configure postgres to trust connections on the same network. This is handy for other containers to connect without authentication. To enable this behavior, set `PG_TRUST_LOCALNET` to `true`. - -For example, +*This feature is only available in releases greater than `9.1-1`* ```bash -docker run --name postgresql -d \ - -e 'PG_TRUST_LOCALNET=true' \ +docker run --name postgresql -itd --restart always \ + --env 'DB_NAME=dbname1,dbname2' \ sameersbn/postgresql:9.4-8 ``` -This has the effect of adding the following to the `pg_hba.conf` file: +# Enabling unaccent extension -``` -host all all samenet trust -``` +Unaccent is a text search dictionary that removes accents (diacritic signs) from lexemes. It's a filtering dictionary, which means its output is always passed to the next dictionary (if any), unlike the normal behavior of dictionaries. This allows accent-insensitive processing for full text search [[source](http://www.postgresql.org/docs/9.4/static/unaccent.html)]. -# Creating a Snapshot or Slave Database - -You may use the `REPLICATION_MODE` variable along with `REPLICATION_HOST`, `REPLICATION_PORT`, `REPLICATION_USER` and `REPLICATION_PASS` to create a snapshot of an existing database and enable stream replication. - -Your master database must support replication or super-user access for the credentials you specify. The `REPLICATION_MODE` variable should be set to `master`, for replication on your master node and `slave` or `snapshot` respectively for streaming replication or a point-in-time snapshot of a running instance. - -Create a master instance +You can enable the unaccent extension on database(s) by specifying `DB_UNACCENT=true`. For example, the following command enables the unaccent extension for the `dbname` database. ```bash -docker run --name='psql-master' -it --rm \ - -e 'REPLICATION_MODE=master' -e 'PG_TRUST_LOCALNET=true' \ - -e 'REPLICATION_USER=replicator' -e 'REPLICATION_PASS=replicatorpass' \ - -e 'DB_NAME=dbname' -e 'DB_USER=dbuser' -e 'DB_PASS=dbpass' \ +docker run --name postgresql -itd \ + --env 'DB_NAME=dbname' --env 'DB_UNACCENT=true' \ sameersbn/postgresql:9.4-8 ``` -Create a streaming replication instance +*By default the unaccent extension is disabled* + +## Granting user access to a database + +If the `DB_USER` and `DB_PASS` variables are specified along with the `DB_NAME` variable, then the user specified in `DB_USER` will be granted access to all the databases listed in `DB_NAME`. Note that if the user and/or databases do not exist, they will be created. ```bash -docker run --name='psql-slave' -it --rm \ - --link psql-master:psql-master \ - -e 'REPLICATION_MODE=slave' -e 'PG_TRUST_LOCALNET=true' \ - -e 'REPLICATION_HOST=psql-master' -e 'REPLICATION_PORT=5432' \ - -e 'REPLICATION_USER=replicator' -e 'REPLICATION_PASS=replicatorpass' \ +docker run --name postgresql -itd --restart always \ + --env 'DB_USER=dbuser' --env 'DB_PASS=dbuserpass' \ + --env 'DB_NAME=dbname1,dbname2' \ sameersbn/postgresql:9.4-8 ``` -# Enable Unaccent (Search plain text with accent) +In the above example `dbuser` with be granted access to both the `dbname1` and `dbname2` databases. -Unaccent is a text search dictionary that removes accents (diacritic signs) from lexemes. It's a filtering dictionary, which means its output is always passed to the next dictionary (if any), unlike the normal behavior of dictionaries. This allows accent-insensitive processing for full text search. +## Creating replication user -By default unaccent is configure to `false` +Similar to the creation of a database user, a new PostgreSQL replication user can be created by specifying the `REPLICATION_USER` and `REPLICATION_PASS` variables while starting the container. ```bash -docker run --name postgresql -d \ - -e 'DB_UNACCENT=true' \ +docker run --name postgresql -itd --restart always \ + --env 'REPLICATION_USER=repluser' --env 'REPLICATION_PASS=repluserpass' \ sameersbn/postgresql:9.4-8 ``` -# Host UID / GID Mapping +> **Notes** +> +> - The created user can login remotely +> - The container will error out if a password is not specified for the user +> - No changes will be made if the user already exists +> - Only a single user can be created at each launch -Per default the container is configured to run postgres as user and group `postgres` with some unknown `uid` and `gid`. The host possibly uses these ids for different purposes leading to unfavorable effects. From the host it appears as if the mounted data volumes are owned by the host's user/group `[whatever id postgres has in the image]`. +*It is a good idea to create a replication user even if you are not going to use it as it will allow you to setup slave nodes and/or generate snapshots when the need arises.* -Also the container processes seem to be executed as the host's user/group `[whatever id postgres has in the image]`. The container can be configured to map the `uid` and `gid` of `postgres` to different ids on host by passing the environment variables `USERMAP_UID` and `USERMAP_GID`. The following command maps the ids to user and group `postgres` on the host. +## Setting up a replication cluster + +When the container is started, it is by default configured to act as a master node in a replication cluster. This means that you can scale your PostgreSQL database backend when the need arises without incurring any downtime. However do note that a replication user must exist on the master node for this to work. + +Begin by creating the master node of our cluster: ```bash -docker run --name=postgresql -it --rm [options] \ - --env="USERMAP_UID=$(id -u postgres)" --env="USERMAP_GID=$(id -g postgres)" \ +docker run --name postgresql-master -itd --restart always \ + --env 'DB_USER=dbuser' --env 'DB_PASS=dbuserpass' --env 'DB_NAME=dbname' \ + --env 'REPLICATION_USER=repluser' --env 'REPLICATION_PASS=repluserpass' \ sameersbn/postgresql:9.4-8 ``` +Notice that no additional arguments are specified while starting the master node of the cluster. -# Upgrading +To create a replication slave the `REPLICATION_MODE` variable should be set to `slave` and additionally the `REPLICATION_HOST`, `REPLICATION_PORT`, `REPLICATION_SSLMODE`, `REPLICATION_USER` and `REPLICATION_PASS` variables should be specified. -To upgrade to newer releases, simply follow this 3 step upgrade procedure. - -- **Step 1**: Stop the currently running image +Create a slave node: ```bash -docker stop postgresql +docker run --name postgresql-slave01 -itd --restart always \ + --link postgresql-master:master \ + --env 'REPLICATION_MODE=slave' -e 'REPLICATION_SSLMODE=prefer' \ + --env 'REPLICATION_HOST=master' -e 'REPLICATION_PORT=5432' \ + --env 'REPLICATION_USER=repluser' --env 'REPLICATION_PASS=repluserpass' \ + sameersbn/postgresql:9.4-8 ``` -- **Step 2**: Update the docker image. +*In the above command, we used docker links so that we can address the master node using the `master` alias in `REPLICATION_HOST`.* + +> **Note** +> +> - The default value of `REPLICATION_PORT` is `5432` +> - The default value of `REPLICATION_SSLMODE` is `prefer` +> - The value of `REPLICATION_USER` and `REPLICATION_PASS` should be the same as the ones specified on the master node. + +And just like that with minimal effort you have a PostgreSQL replication cluster setup. You can create additional slaves to scale the cluster horizontally. + +Here are some important notes about a PostgreSQL replication cluster: + + - Writes can only occur on the master + - Slaves are read-only + - For best performance, limit the reads to the slave nodes + +## Creating a snapshot + +Similar to a creating replication slave node, you can create a snapshot of the master by specifying `REPLICATION_MODE=snapshot`. + +Once the master node is created as specified in [Setting up a replication cluster](#setting-up-a-replication-cluster), you can create a snapshot using: ```bash -docker pull sameersbn/postgresql:9.4-8 +docker run --name postgresql-snapshot -itd --restart always \ + --link postgresql-master:master \ + --env 'REPLICATION_MODE=snapshot' -e 'REPLICATION_SSLMODE=prefer' \ + --env 'REPLICATION_HOST=master' -e 'REPLICATION_PORT=5432' \ + --env 'REPLICATION_USER=repluser' --env 'REPLICATION_PASS=repluserpass' \ + sameersbn/postgresql:9.4-8 ``` -- **Step 3**: Start the image +The difference between a slave and a snapshot is that a slave is read-only and updated whenever the master data is updated (streaming replication), while a snapshot is read-write and is not updated after the initial snapshot of the data on the master. + +This is useful for developers to quickly snapshot the current state of a live database and use it for development/debugging purposes without altering the database on the live instance. + +## Logs + +By default the PostgreSQL server logs are sent to the standard output. Using the [Command-line arguments](#command-line-arguments) feature you can configure the PostgreSQL server to send the log output to a file using the `-c logging_collector=on` argument: ```bash -docker run --name postgresql -d [OPTIONS] sameersbn/postgresql:9.4-8 +docker run --name postgresql -itd --restart always \ + sameersbn/postgresql:9.4-8 -c logging_collector=on ``` -# Shell Access +To access the PostgreSQL logs you can use `docker exec`. For example: -For debugging and maintenance purposes you may want access the containers shell. If you are using docker version `1.3.0` or higher you can access a running containers shell using `docker exec` command. +```bash +docker exec -it postgresql tail -f /var/log/postgresql/postgresql-9.4-main.log +``` + +# UID/GID mapping + +The files and processes created by the container are owned by the `postgres` user that is internal to the container. In the absense of user namespace in docker the UID and GID of the containers `postgres` user may have different meaning on the host. + +For example, a user on the host with the same UID and/or GID as the `postgres` user of the container will be able to access the data in the persistent volumes mounted from the host as well as be able to KILL the `postgres` server process started by the container. + +To circumvent this issue you can specify the UID and GID for the `postgres` user of the container using the `USERMAP_UID` and `USERMAP_GID` variables respectively. + +For example, if you want to assign the `postgres` user of the container the UID and GID `999`: + +```bash +docker run --name postgresql -itd --restart always \ + --env 'USERMAP_UID=999' --env 'USERMAP_GID=999' \ + sameersbn/postgresql:9.4-8 +``` + +# Maintenance + +## Upgrading + +To upgrade to newer releases: + + 1. Download the updated Docker image: + + ```bash + docker pull sameersbn/postgresql:9.4-8 + ``` + + 2. Stop the currently running image: + + ```bash + docker stop postgresql + ``` + + 3. Remove the stopped container + + ```bash + docker rm -v postgresql + ``` + + 4. Start the updated image + + ```bash + docker run --name postgresql -itd \ + [OPTIONS] \ + sameersbn/postgresql:9.4-8 + ``` + +## Shell Access + +For debugging and maintenance purposes you may want access the containers shell. If you are using Docker version `1.3.0` or higher you can access a running containers shell by starting `bash` using `docker exec`: ```bash docker exec -it postgresql bash ``` - -If you are using an older version of docker, you can use the [nsenter](http://man7.org/linux/man-pages/man1/nsenter.1.html) linux tool (part of the util-linux package) to access the container shell. - -Some linux distros (e.g. ubuntu) use older versions of the util-linux which do not include the `nsenter` tool. To get around this @jpetazzo has created a nice docker image that allows you to install the `nsenter` utility and a helper script named `docker-enter` on these distros. - -To install `nsenter` execute the following command on your host, - -```bash -docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter -``` - -Now you can access the container shell using the command - -```bash -sudo docker-enter postgresql -``` - -For more information refer https://github.com/jpetazzo/nsenter