flyway unable to connect to postgres container within docker-entrypoint-initdb.d script - postgresql

I'm trying to extend the postgres Docker image to potentially (via an environment variable flag) execute flyway DB migrations on DB init. My Dockerfile is here:
FROM postgres:9.6
# Install curl and java (for Flyway)
RUN set -x \
&& apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl openjdk-8-jre
# Install Flyway
ENV FLYWAY_VERSION 4.2.0
ENV FLYWAY_INSTALL_DIR /usr/src/flyway
ENV FLYWAY_CONF ${FLYWAY_INSTALL_DIR}/flyway-${FLYWAY_VERSION}/conf/flyway.conf
ENV FLYWAY_EXE ${FLYWAY_INSTALL_DIR}/flyway-${FLYWAY_VERSION}/flyway
RUN mkdir -p ${FLYWAY_INSTALL_DIR} && \
curl -L https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/${FLYWAY_VERSION}/flyway-commandline-${FLYWAY_VERSION}.tar.gz | \
tar -xzC ${FLYWAY_INSTALL_DIR} && \
chmod +x ${FLYWAY_EXE}
# Copy migration scripts
ENV MIGRATIONS_LOCATION /flyway/migrations
COPY migrations $MIGRATIONS_LOCATION
COPY init_db.sh /docker-entrypoint-initdb.d/init_db.sh
With my init_db.sh startup script:
#!/bin/bash
set -e
RUN_MIGRATIONS="${RUN_MIGRATIONS:-false}"
DB_URL="jdbc:postgresql://localhost:5432/$DB_NAME"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
CREATE DATABASE $DB_NAME;
EOSQL
if [ "$RUN_MIGRATIONS" == "true" ]; then
echo "running migrations ..."
${FLYWAY_EXE} -user=$POSTGRES_USER -password=$POSTGRES_PASSWORD -url=$DB_URL -locations="filesystem:$MIGRATIONS_LOCATION" migrate
fi
However, when running the container with RUN_MIGRATIONS=true, flyway fails to connect to postgres:
docker build . -t postgres-flyway && docker run -e DB_NAME=db -e RUN_MIGRATIONS=true -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres postgres-flyway
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are disabled.
fixing permissions on existing directory /var/lib/postgresql/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting dynamic shared memory implementation ... posix
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok
WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.
Success. You can now start the database server using:
pg_ctl -D /var/lib/postgresql/data -l logfile start
waiting for server to start....LOG: database system was shut down at 2018-08-06 02:19:32 UTC
LOG: MultiXact member wraparound protections are now enabled
LOG: autovacuum launcher started
LOG: database system is ready to accept connections
done
server started
ALTER ROLE
/usr/local/bin/docker-entrypoint.sh: sourcing /docker-entrypoint-initdb.d/init_db.sh
CREATE DATABASE
running migrations ...
Flyway 4.2.0 by Boxfuse
ERROR:
Unable to obtain Jdbc connection from DataSource (jdbc:postgresql://localhost:5432/db) for user 'postgres': Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL State : 08001
Error Code : 0
Message : Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
The postgres image runs postgres on port 5432 (as usual) so I'm at a loss on why flyway is unable to connect to postgres over localhost:5432.
I also noticed that within this context, pg_isready states that postgres is accepting connections but when specifying the hostname as localhost or 127.0.0.1 it is unable to reach postgres either. That is, by inserting a few pg_isready commands in my init_db.sh script:
...
pg_isready
pg_isready -p 5432
pg_isready -h localhost -p 5432
...
I see the following log output on postgres init:
...
/var/run/postgresql:5432 - accepting connections
/var/run/postgresql:5432 - accepting connections
localhost:5432 - no response
...
I'm suspicious that I've reached a limitation of postgres' initialize context, but I would like to understand why postgres is unreachable over localhost/127.0.0.1:5432 at this point of initialization.

I had the same problem running flyway when creating a docker image for my database based on the postgres:10.5 image. I added the following to my entrypoint.sh before running flyway, to confirm that the problem I was seeing was caused by the docker-entrypoint.sh change #Nick Maraston posted in his answer:
echo "$(date) - waiting for database to start"
while ! pg_isready -h localhost -p 5432 -d $POSTGRES_DB
do
echo "$(date) - waiting for database to start"
sleep 10
done
The result was that the above code looped for ever. I then replaced it with the following code to restart the database listening for TCP/IP connections on localhost:
pg_ctl -D "$PGDATA" -m fast -w stop
pg_ctl -D "$PGDATA" \
-o "-c listen_addresses='localhost'" \
-w start
Rather than restarting the database like this, a cleaner solution would be to use the JDBC -socketFactory option explained here.

I discovered the problem while digging through the images entry point script. A recent change to the image restricts postgres to only listen for connections over a unix domain socket during internal initialization: https://github.com/docker-library/postgres/pull/440

It is true that postgres docker is listening to a unix socket, eg. /var/run/postgresql/.s.PGSQL.5432. But it is not necessary to force the server to switch its listening address. Postgres database URI allows a connection string to point to a unix socket.
Reference: Connect to a database over a unix socket using SQLAlchemy
The example provided:
export DATABASE_URL=postgres://user:password#/dbname?host=/path/to/unix/socket
I was able to omit the host, and decided to use this environment variable in my /docker-entrypoint-initdb.d/*.sh script instead. Note that no string follows the # symbol, nor is there a host query string here. You may need to explicitly define the host depending on your application.
Solution:
export DATABASE_URL="postgres://$POSTGRES_USER:$POSTGRES_PASSWORD#/$POSTGRES_DB"

Related

Connecting to postgres while inside postgres docker container

I am trying to set up a custom Dockerfile for postgres that runs flyway migrations on startup. So far I've managed to get everything built, installed and executable, but I am unable to get flyway to connect to the postgres database inside the docker container on startup due to connection errors.
Before you tell me to set it up in a docker-compose, file I would like to add that I am doing this so that I can use the docker image in gitlab ci/cd as a service container, so using docker-compose is not an option.
Here is my dockerfile
FROM postgres:12 AS db
WORKDIR /home
# Install wget
RUN apt-get update -y \
&& apt-get install wget -y \
&& apt-get install sudo -y \
&& apt-get install lsof
RUN usermod -aG sudo postgres
RUN bash -c 'echo "postgres ALL=(ALL:ALL) NOPASSWD: ALL" | (EDITOR="tee -a" visudo)'
# Install latest version of flyway 7
RUN wget -qO- https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/7.9.2/flyway-commandline-7.9.2-linux-x64.tar.gz | tar xvz && \
ln -s `pwd`/flyway-7.9.2/flyway /usr/local/bin
# Copy database migrations for flyway
COPY reds/migrations migrations
# Set environment variables for flyway
ENV FLYWAY_LOCATIONS="/home/migrations"
ENV FLYWAY_SCHEMAS="reds"
ENV FLYWAY_CONNECT_RETRIES=5
ENV FLYWAY_BASELINE_ON_MIGRATE=false
ENV FLYWAY_OUT_OF_ORDER=false
# Make postgres startup script run flyway migrations
COPY reds/init.sh /docker-entrypoint-initdb.d/init.sh
RUN chmod +x /docker-entrypoint-initdb.d/init.sh && \
chown -R postgres /docker-entrypoint-initdb.d
EXPOSE 5432
CMD ["postgres"]
And the following is the script that is executed on container startup
#!/bin/bash
set -eou pipefail
# I've tried localhost, 0.0.0.0 and 127.0.0.1
host="$(hostname -i)"
sudo flyway migrate \
-url="jdbc:postgresql://$host:5432/$POSTGRES_DB" \
-schemas="$FLYWAY_SCHEMAS" \
-locations="$FLYWAY_LOCATIONS" \
-connectRetries="$FLYWAY_CONNECT_RETRIES" \
-baselineOnMigrate="$FLYWAY_BASELINE_ON_MIGRATE" \
-outOfOrder="$FLYWAY_OUT_OF_ORDER" \
-user="$POSTGRES_USER" \
-password="$POSTGRES_PASSWORD"
My build command:
$ docker build -t reds-docker:latest -f "Dockerfile.reds" .
My run command:
$ docker run --rm \
--env POSTGRES_USER="postgres" \
--env POSTGRES_PASSWORD="postgres" \
--env POSTGRES_DB="postgres" \
reds-docker:latest
I get the following error for flyway trying to connect to the database when I run the container:
WARNING: Connection error: Connection to 172.17.0.2:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections. (Caused by Connection refused (Connection refused))
Full log from startup:
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are disabled.
fixing permissions on existing directory /var/lib/postgresql/data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Etc/UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok
Success. You can now start the database server using:
pg_ctl -D /var/lib/postgresql/data -l logfile start
initdb: warning: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.
waiting for server to start....2023-02-06 13:36:59.228 UTC [48] LOG: starting PostgreSQL 12.13 (Debian 12.13-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
2023-02-06 13:36:59.229 UTC [48] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2023-02-06 13:36:59.237 UTC [49] LOG: database system was shut down at 2023-02-06 13:36:59 UTC
2023-02-06 13:36:59.239 UTC [48] LOG: database system is ready to accept connections
done
server started
/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sh
WARNING: This version of Flyway is out of date. Upgrade to Flyway 9.14.1:https://flywaydb.org/documentation/learnmore/staying-up-to-date/?ref=v7.9.2_cmd-line
Flyway Community Edition 7.9.2 by Redgate
WARNING: Connection error: Connection to 172.17.0.2:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections. (Caused by Connection refused (Connection refused)) Retrying in 1 sec...
WARNING: Connection error: Connection to 172.17.0.2:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections. (Caused by Connection refused (Connection refused)) Retrying in 2 sec...
Just to be clear, the scripts in docker-entrypoint-initdb.d aren't run on container start as you say. They're run on database initialization. So they're only run when a fresh database is initialized and as such don't work well for migrations that usually change an existing database.
That being said, the reason your script isn't working is that while the database is being initialized, Postgres is running in a state where it isn't accepting TCP/IP connections. You can only talk to it using a Unix socket. You can see that in the log in the message listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432".
I don't know if Flyway supports connecting over a Unix socket. But you can't connect over TCP/IP in a database initialization script.
The code that starts the database is this part of the container startup script:
# start socket-only postgresql server for setting up or running scripts
# all arguments will be passed along as arguments to `postgres` (via pg_ctl)
docker_temp_server_start() {
if [ "$1" = 'postgres' ]; then
shift
fi
# internal start of server in order to allow setup using psql client
# does not listen on external TCP/IP and waits until start finishes
set -- "$#" -c listen_addresses='' -p "${PGPORT:-5432}"
PGUSER="${PGUSER:-$POSTGRES_USER}" \
pg_ctl -D "$PGDATA" \
-o "$(printf '%q ' "$#")" \
-w start
}
As you can see, it sets listen_addresses to a blank string and that causes Postgres to not listen on any TCP/IP ports. There doesn't seem to be a way to modify this behaviour without creating your own image with a modified startup script.

How to use postgresql in nixos

I've installed posgresql:
nix-env -iA nixos.postgresql
Now I wanto use it.
psql
psql: error: connection to server on socket "/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
Is the server running locally and accepting connections on that socket?
Must I add something in /etc/nixos/configuration.nix ?
update
I've tried to do like this link. i.e. install in nix-shell and not in nix-env.
I've the same shell.nix
nix-shell --pure shell.nix
done
server started
psql
psql: error: could not connect to server: could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/mnt/c/Users/Pierre-Olivier/nix/psql/.pg/.s.PGSQL.5432"?
psql is the PostgreSQL interactive terminal.
What you are trying to do is connect to database without PostgreSQL running in first place.
Follow these steps:
Initialize the database with initdb -D .data
Start a PostgreSQL server with pg_ctl -D .data -l logfile start
Make sure it's running pg_ctl -D .data status
Connect to database with psql -d postgres (by default, postgres database is created)
At the end, make sure to stop database with pg_ctl -D .data stop when leaving the nix shell.

Connect to postgres with custom database cluster location with psql

Instead of using /usr/local/pgsql/data, I create my database cluster with the following code
initdb -D /tmp/psql
pg_ctl -D /tmp/psql -l logfile -o "--unix_socket_directories='$PWD'" start
But when I run psql, I get the following error.
psql: error: could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/tmp/.s.PGSQL.5432"?
I have read the doc and I cannot find related options for changing the location for database cluster. What have I missed?
I ran into the same issue and specifying the socket directory with the -h option fixes the issue.
psql -h $PWD -p 5432 postgres

Can't run script inside postgres container

Assume we need to run second postgres instance in our production environment. First of them (postgres-one) already running and has few databases & data in them. Now I want to update my docker-compose.yaml file and add configuration for the second (postgres-two). Moreover I have to grab some databases info from postgres-one and copy it to postgres-two. Here how I'm trying to achieve this:
docker-compose.yaml
postgres-two:
image: postgres:12.5
depends_on:
postgres-one:
condition: service_started
...
ports:
- "5433:5432"
command: bash -c "chmod +x /usr/local/bin/init.sh && /usr/local/bin/init.sh"
volumes:
- ./data/postgres-two/init-db/init.sh:/usr/local/bin/init.sh
init.sh
#!/bin/bash
# allows you to skip the password prompt for pg_dump
echo "postgres-one:5432:dbname1_one:dbuser1_one:dbpass1_one" > ~/.pgpass
echo "postgres-one:5432:dbname2_one:dbuser2_one:dbpass2_one" >> ~/.pgpass
chmod 600 ~/.pgpass
# gets the data from external database & copies it to internal
pg_dump -h postgres-one -U dbuser1_one dbname1_one | psql -h localhost -U dbuser1_two -d dbname1_two
pg_dump -h postgres-one -U dbuser2_one dbname2_one| psql -h localhost -U dbuser2_two -d dbname2_two
But when I run this I get the error:
psql: error: could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP/IP connections on port 5432?
I already tried it w/o -h localhost before, it gave me this as I remember:
psql: error: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
If I add find /var/run/postgresql/ -name .s.PGSQL.5432 in the beginning of the init.sh it'll show nothing. So, as I understand I can't proceed further with psql because postgres server is not running at the moment. And of course I can't run it with postgres / pg_ctl commands because they can't be executed by root:
"root" execution of the PostgreSQL server is not permitted.
And of course docker containers run as the root user by default, and if I change the user it also give me errors such as:
chmod: changing permissions of '/usr/local/bin/init.sh': Operation not permitted
Am I doing something wrong? Or maybe I can get the dumps and apply them in other ways.. somehow?
I think server is failing to start because of volume mapping,
You can refer to this yml,
https://github.com/khezen/compose-postgres/blob/master/docker-compose.yml
Nvm, I solved the problem by placing the init.sh script into the docker-entrypoint-initdb.d folder. But then I also had an access issue with pg_dump because for some reason .pgpass didn't work in my case. For those, who will face the same problem, I fixed it with PGPASSWORD env like that:
PGPASSWORD="dbpass1_one" pg_dump -h postgres-one -U dbuser1_one dbname1_one | psql -h localhost -U dbuser1_two -d dbname1_two

How to properly set VOLUME and CMD instructions in Postgres Dockerfile?

I have a working Postgres Dockerfile that I modify and unfortunately after applying modifications Postgres container stops working as expected. I'd like to ask your for explanation of what I'm doing wrong.
Working example
Here's the Postgres Dockerfile that works and which I modify:
# Use ubuntu image
FROM ubuntu
# Install database
RUN apt-get update && apt-get install -y postgresql-9.3
# Switch to postgres user.
USER postgres
# Create databse and user with all privileges to the database.
RUN /etc/init.d/postgresql start && \
psql --command "CREATE DATABASE docker;" && \
psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" &&\
psql --command "GRANT ALL PRIVILEGES ON DATABASE docker TO docker;"
# Allow remote connections to the database.
RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf
RUN echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf
# Add VOLUMEs to allow backup of config, logs and databases
VOLUME ["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"]
# Set the default command to run when starting the container
CMD ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"]
I build it like that:
docker build --tag postgres-image .
Then I create a container:
docker run -d -it -p 32768:5432 --name=postgres postgres-image
And I connect with database:
psql -h localhost -p 32768 -d docker -U docker --password
First modification
I don't need to have any volumes because I'm going to use data-only container that will store all Postgres data. When I remove the line:
VOLUME ["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"]
and do all steps like in working example I get the following error after passing password in the last step:
psql: FATAL: the database system is starting up
FATAL: the database system is starting up
So the question is: Why do I need VOLUME instruction in the Dockerfile?
Second modification
This modification doesn't include the first one. Both modification are independent.
The parameters used in CMD instraction points to default Postgres data directory and configuration file so I wanted to simplify it by setting CMD to the command I always use to start Posgres:
service postgres start
After setting CMD to:
CMD ["service", "postgres", "start]
and doing all steps like in working example I get the following error after passing password in the last step:
psql: could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP/IP connections on port 32768?
The question is: Why the command that works on my host system doesn't work in Docker container?
I'm not sure about the first problem. It may be that Postgres doesn't like running on top of the UFS.
The second problem is just that a container will exit when its main process ends. So the command "service postgres start" runs, starts Postgres in the background then immediately exits and the container halts. The first version works because Postgres stays running in the foreground.
But why are you doing this? Why not just use the official Postgres image?