384

My docker compose file has three containers, web, nginx, and postgres. Postgres looks like this:

postgres:
  container_name: postgres
  restart: always
  image: postgres:latest
  volumes:
    - ./database:/var/lib/postgresql
  ports:
    - 5432:5432

My goal is to mount a volume which corresponds to a local folder called ./database inside the postgres container as /var/lib/postgres. When I start these containers and insert data into postgres, I verify that /var/lib/postgres/data/base/ is full of the data I'm adding (in the postgres container), but in my local system, ./database only gets a data folder in it, i.e. ./database/data is created, but it's empty. Why?

Notes:

UPDATE 1

Per Nick's suggestion, I did a docker inspect and found:

    "Mounts": [
        {
            "Source": "/Users/alex/Documents/MyApp/database",
            "Destination": "/var/lib/postgresql",
            "Mode": "rw",
            "RW": true,
            "Propagation": "rprivate"
        },
        {
            "Name": "e5bf22471215db058127109053e72e0a423d97b05a2afb4824b411322efd2c35",
            "Source": "/var/lib/docker/volumes/e5bf22471215db058127109053e72e0a423d97b05a2afb4824b411322efd2c35/_data",
            "Destination": "/var/lib/postgresql/data",
            "Driver": "local",
            "Mode": "",
            "RW": true,
            "Propagation": ""
        }
    ],

Which makes it seem like the data is being stolen by another volume I didn't code myself. Not sure why that is. Is the postgres image creating that volume for me? If so, is there some way to use that volume instead of the volume I'm mounting when I restart? Otherwise, is there a good way of disabling that other volume and using my own, ./database?

vvvvv
  • 25,404
  • 19
  • 49
  • 81
Alex Lenail
  • 12,992
  • 10
  • 47
  • 79
  • do you already run the `initdb` command line to initialize your database cluster? – Sebastian Webber Jan 13 '17 at 15:31
  • 1
    Are you sure your data subdirectory is really empty? It might have special access permissions. – Yaroslav Stavnichiy Jan 13 '17 at 15:32
  • Thanks for getting back to me so fast! I'm using a flask app, so I `from app import db` and `db.create_all()` from a `docker run` after starting the containers. I don't `initdb` directly from the command line. – Alex Lenail Jan 13 '17 at 15:45
  • 1
    @YaroslavStavnichiy I don't know how else to check that than `sudo su -` and look in `./database/data`. There's nothing in there as far as I can tell. – Alex Lenail Jan 13 '17 at 15:48
  • Someone might find this useful: sample compose file persisting postgres, elastic search and media data, https://stackoverflow.com/a/56475980/5180118 – ArdentLearner Jun 06 '19 at 10:48
  • Seems like if you specify a path as the first, this creates a bind_mount instead of a volume? – Phuah Yee Keat Oct 09 '19 at 03:24

5 Answers5

481

Strangely enough, the solution ended up being to change

volumes:
  - ./postgres-data:/var/lib/postgresql

to

volumes:
  - ./postgres-data:/var/lib/postgresql/data
Alex Lenail
  • 12,992
  • 10
  • 47
  • 79
  • 89
    Just a quick "why" for this answer (which works). Per the postgres folks, the default data directory is `/var/lib/postgresql/data` - you can read the PGDATA variable notes here: https://store.docker.com/images/022689bf-dfd8-408f-9e1c-19acac32e57b?tab=description – Matt Pavelle Apr 25 '17 at 20:49
  • 4
    And add the local directory to your `.dockerignore` file, especially if you'll ever trun this into a production image. See https://codefresh.io/blog/not-ignore-dockerignore/ for a discussion. – dsz Dec 31 '17 at 08:35
  • 4
    this does still not work for me (mac os x high sierra) – olidem Mar 02 '18 at 13:30
  • 2
    @OlliD-Metz I had to do a `docker rm my_postgres_container_1` before it worked (also High Sierra). – rich Mar 21 '18 at 10:14
  • @rich not very helpful tip :) – bottaio Mar 27 '19 at 15:47
  • 2
    If you have run the docker-compose during test without specifying the volume info, I think that you will have automatically created volumes which persist. In my case, I had to manually delete them before the container would use the included volume info. My solution turned out to be: docker volume rm – frozenjim Apr 25 '20 at 11:25
  • Make sure to check the logs when starting the image back up again. In my case I got an error because the local directory I was trying to mount to wasn't empty. – Matt Browne Aug 25 '21 at 19:35
  • Also I had an issue where it wasn't realizing the directory was empty if I deleted the data directory and started it up again. Adding a trailing slash to the local folder fixed that (e.g. `- ./postgres-data/` instead of `- ./postgres-data`) – Matt Browne Aug 25 '21 at 19:58
  • Can someone explain why it works? The first (highly upvoted) comment just states the obvious. – Ivan Kleshnin Aug 04 '22 at 14:12
  • upvoted what is the difference between using a named volume for postgres vs a directory? – PirateApp Aug 24 '22 at 07:00
191

You can create a common volume for all Postgres data

docker volume create pgdata

or you can set it to the compose file

version: "3"
services:
  db:
    image: postgres
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgress
      - POSTGRES_DB=postgres
    ports:
      - "5433:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - suruse
volumes:
  pgdata:

It will create volume name pgdata and mount this volume to container's path.

You can inspect this volume

docker volume inspect pgdata
// output will be
[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
        "Name": "pgdata",
        "Options": {},
        "Scope": "local"
    }
]

upe
  • 1,862
  • 1
  • 19
  • 33
Nishchit
  • 18,284
  • 12
  • 54
  • 81
  • 15
    Commenting a bit late, but won't this clear data if I do a `docker-compose down -v`. And what is the solution to that? – Sid Nov 06 '17 at 09:17
  • 10
    @Sid, yes, it will! Just be careful with this option. – olidem Mar 26 '18 at 15:01
  • 4
    so with docker-compose [down] the volume is no longer persisted? Does a full cleanup even of the volume? – Paul Aug 13 '19 at 00:33
  • 12
    @Sid Commenting even later, but you *can* use `docker-compose down --rmi all` *without* the `-v` option and it'll clear out "everything" *except* the volumes, i.e. containers, networks, images, etc. I do that when deploying while allowing data persistence. – code_dredd Oct 09 '19 at 18:29
  • 4
    What is the `suruse` for? – Ian CT Jun 21 '21 at 16:54
  • 1
    @IanChuTe It's a network name, you can give any name best suited to your project. – Nishchit Oct 18 '21 at 07:09
18

I would avoid using a relative path. Remember that docker is a daemon/client relationship.

When you are executing the compose, it's essentially just breaking down into various docker client commands, which are then passed to the daemon. That ./database is then relative to the daemon, not the client.

Now, the docker dev team has some back and forth on this issue, but the bottom line is it can have some unexpected results.

In short, don't use a relative path, use an absolute path.

Nick Burke
  • 949
  • 9
  • 10
  • Thanks for this answer! Sadly, I don't think it worked. I changed the line to an absolute path, and after inserting the data, the `database/data` folder is still empty =( – Alex Lenail Jan 13 '17 at 19:16
  • K. Next up is to run `docker inspect` on the container and make sure that the container is aware of the volume (just in case compose is confused or something). (note: docker inspect can have sensitive data, so don't paste it here without munging ;-) After that, it's a matter of checking permissions (although that would usually show an error) – Nick Burke Jan 13 '17 at 19:22
  • Aha! @Nick Burke I think you've found something. I've updated the question. – Alex Lenail Jan 13 '17 at 23:27
5

The postgres image's Dockerfile contains this line:

VOLUME /var/lib/postgresql/data

When a container starts up based on this image, if nothing else is mounted there, Docker will create an anonymous volume and automatically mount it. You can see this volume using commands like docker volume ls, and that's also the second mount in the docker inspect output you quote.

The main consequence of this is that your external volume mount must be on /var/lib/postgresql/data and not a parent directory. (Also see @AlexLenail's answer.)

If you mount a host directory on /var/lib/postgresql instead, then:

  1. Docker internally sorts the mounts, so parent directories get mounted first.
  2. The host directory is mounted on /var/lib/postgresql.
  3. Nothing is mounted on /var/lib/postgresql/data, so Docker creates the anonymous volume.
  4. The mount point does not exist (it is in the empty bind-mounted host directory) so Docker creates it, also creating the ./database/data directory on the host.
  5. The anonymous volume is mounted on that directory, but only in the container filesystem.

The result of this is what you see, the database apparently operates correctly but its data is not necessarily persisted and you get only the empty data directory on the host.

David Maze
  • 130,717
  • 29
  • 175
  • 215
3

I think you just need to create your volume outside docker first with a docker create -v /location --name and then reuse it.

And by the time I used to use docker a lot, it wasn't possible to use a static docker volume with dockerfile definition so my suggestion is to try the command line (eventually with a script ) .

leldo
  • 386
  • 2
  • 9