How do I both expose the port on a container to the local host AND bind persistent data to the container?

Issue

I am using dockerode to create a new PostgreSQL docker container. I want to be able to:

  1. Expose port 5432 on the local host
  2. Have the container use a directory on the local host as the persistent data store

I’ve been able to get a working implementation that creates a Docker container and exposes port 5432 on the local host with the following code:

docker.createContainer({
      Image: "postgres",
      Env: [`POSTGRES_PASSWORD=postgres`],
      HostConfig: {
        PortBindings: {
          "5432/tcp": [{ HostPort: "5432" }]
        },
      },
      ExposedPorts: {
        "5432/tcp": {}
      },
      name: "container-name"
})
  • In this implementation, the data is not persistent – restarting the container will always start from a “fresh” DB
    • I need to have persistent data

From reading the Docker REST API documentation (and through reading lots of community posts), I found that I need to use the Binds parameter. I updated my code to the following:

docker.createContainer({
      Image: "postgres",
      Env: [`POSTGRES_PASSWORD=postgres`],
      HostConfig: {
        PortBindings: {
          "5432/tcp": [{ HostPort: "5432" }]
        },
        // NEW CODE
        Binds: [
          "/path/on/host/machine:/path/on/container"
        ],
      },
      ExposedPorts: {
        "5432/tcp": {}
      },
      name: "container-name"
})
  • However, after adding the Binds parameter, I am no longer able to connect to the database from the host machine

Can anyone tell me what I am doing wrong here? I can’t figure out what Binds needs to be in this particular setup. Eventually, I won’t need the database exposed to the local machine, but for now, I need to be able to for testing purposes. Thanks in advance!

UPDATE 1 – 6/15/2019

I retested this with a similar setup as suggested, which really wasn’t all that different from what I was already doing. However, the suggestions helped me get on the right track and understand where my error was.

It seems that the problem was happening because I was trying to reuse a directory on my local host that had already been used for a different database setup. I thought this was supported; I’m guessing there is something I am missing?

In either case, I have this working now, but I don’t quite get why I can’t reuse the same directory on my host machine that I was using when I start the containers from the command line.

Clarification

I failed to mention that I can get this working by starting the container from the command line. The problem is when trying to use dockerode – however, I don’t think this is a problem with Dockerode, but rather with some misunderstanding I have with the Docker REST API.

I can use the same directory for all databases IF I started the containers from the Command line, but not if I start the containers using the Docker REST API (dockerode in my case).

UPDATE 2 – 6/15/2019

It seems there was something wrong with the path my code was generating. After I went through this a bit, it seems I simply overlooked that error. Strangely, I didn’t get any errors in the container when I checked the logs – that’s why I didn’t think there was anything wrong with the path.

So, for those who encounter this, it may be a simple matter of needing to fix the path you referenced on the host machine.

With that being said, I’ve marked the accepted solution by mchawre – his solution was correct for the original question I asked. The rest of the issues I had were due to a bug, it seems.

(I do wish the Docker logs would have said something like “no such directory” for the container when it started, though – that would have led to a much-quicker resolution)

Relevant Info
I found the following info here:

Warning: the Docker specific variables will only have an effect if you start the container with a data directory that is empty; any pre-existing database will be left untouched on container startup.

  • I think this is why I hit this issue, but not 100% sure yet

Solution

I tried this from my end and everything is working properly. Please refer below steps:

  1. First I installed dockerode on my machine npm install dockerode.
  2. Figured out my machine ip and port on which docker daemon is exposed.
  3. Created run.js file which contains the code to run postgres container with external volumes.
var Docker = require('dockerode');
var dockerHostIP = "192.168.0.33"
var dockerHostPort = 2375

var docker = new Docker({ host: dockerHostIP, port: dockerHostPort });

docker.createContainer({
  Image: "postgres",
  Env: [`POSTGRES_PASSWORD=postgres`],
  HostConfig: {
    PortBindings: {
      "5432/tcp": [{ HostPort: "5432" }]
    },
    // NEW CODE
    Binds: [
      "/root/dir:/tmp"
    ],
  },
  ExposedPorts: {
    "5432/tcp": {}
  },
  name: "container-name"
}).then(function(container) {
  return container.start();
})

NOTE: /root/dir is dir on my host and /tmp is one inside the container.

  1. Ran the js file node run.js
  2. Postgres seems to be running and can connect to it from my host
$ psql postgresql://postgres:postgres@localhost:5432/postgres
psql (11.3)
Type "help" for help.

postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(3 rows)

postgres=#
  1. Also checked the contents of /root/dir directory on my host to see if it is properly mounted.
$ ls /root/dir/
test.txt
$

Please try this from your end and let me know.

Answered By – mchawre

Answer Checked By – Pedro (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.