I'm experiencing a confusing situation that I'd love some additional thoughts about. I'm trying to get a local dev environment set up at my company using Docker.
Goal 1 is to allow local edits to app and lib to be captured and piped into the container. I am able to accomplish this with no problem using bind mounts to those directories. This enables our ruby work to happen against a running docker cluster, perfect!
Goal 2 is to allow updates to public/assets/, generated by a webpack process running on the local filesystem, to be captured and piped into the container. The intention is for our front-end engineers to run webpack locally, but to allow their compiled local output to be served by the running docker container.
Unfortunately, something strange is happening when I try to do this. Currently, as you can see below, I'm trying to use public/ as a bind mount. This sort of works -- I can navigate into public/ and see a bunch of files. But this is where it gets weird. The public/ directory contains assets/, which holds the output of the local webpack process. When I shell into my container I can see the assets folder:
$ cd public
$ ls -la
...
drwxr-xr-x 36 root root 1152 Aug 13 13:12 assets
....
But then when I try to access it I get weird behavior:
/webapp/public # ls -la assets
ls: cannot access 'assets': No such file or directory
Sure enough, when I navigate to my web application it gives me 404's on anything in the public/assets/ folder.
Here's my Dockerfile:
FROM ruby:2.7.2-alpine
WORKDIR /webapp
RUN apk add --update --no-cache yarn nodejs npm graphviz vim postgresql-client coreutils binutils build-base readline readline-dev cmake git nodejs openssh-client openssl-dev postgresql-dev shared-mime-info tzdata; \
mkdir -p -m 0600 ~/.ssh; \
ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN mkdir config
# this overwrites the local database.yml with some custom params we need to use docker-compose's postgres.
# this way you don't need to maintain a forked local config, the container will configure itself correctly.
COPY local/config/application.template.yml config/application.yml
COPY local/config/database.template.yml config/database.yml
COPY local/config/warehouse.template.yml config/warehouse.yml
COPY local/config/.setup_config.template.yml ./.setup_config.yml
ARG packagecloud
ARG contribsys
ENV PACKAGECLOUD_TOKEN=$packagecloud
ENV BUNDLE_GEMS__CONTRIBSYS__COM=$contribsys
# by only copying over config and gem info prior to bundle installing I'm hoping to
# get Docker to use its caching to not run this unless something above has changed.
COPY ./Gemfile ./Gemfile
COPY ./Gemfile.lock ./Gemfile.lock
RUN --mount=type=ssh bundle install
# we should be able to do the same for yarn, but it's not nearly as expensive.
# for some reason, though, the packagecloud stuff fails if we run this before
# copying everything over. Unsure why.
COPY ./package.json ./package.json
COPY ./yarn.lock ./yarn.lock
COPY ./.npmrc ./.npmrc
RUN yarn install
COPY . .
# if necessary, remove the .gitkeep files -- they keep databases from initializing. This sucks for now.
RUN mkdir -p local/data/redis/
RUN mkdir -p local/data/postgres/
EXPOSE 3000
CMD ["/bin/sh"]
Here's my docker-compose.yml with database details redacted:
version: "3"
networks:
default:
name: mode-net
driver: bridge
services:
db:
image: postgres:11.5
environment:
volumes:
- ./local/data/postgres:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: "redis:5-alpine"
command: redis-server
ports:
- "6379:6379"
volumes:
- ./local/data/redis:/var/lib/postgresql/data
environment:
- REDIS_URL_SIDEKIQ=redis://redis:6379/1
sidekiq:
image: webapp:latest
depends_on:
- "db"
- "redis"
command: bin/sidekiq
volumes:
- ./app:/webapp/app
- ./lib:/webapp/lib
environment:
- RAILS_ENV=development
- DATABASE_URL=
- REDIS_URL_SIDEKIQ=redis://redis:6379/1
web:
image: webapp:latest
command: /webapp/local/scripts/start_server
environment:
- RAILS_ENV=development
- DEBUG=$DEBUG
- DATABASE_URL=
- REDIS_URL_SIDEKIQ=redis://redis:6379/1
ports:
- "3000:3000"
- "1234:1234" # used for debugger access
volumes:
- ./public:/webapp/public
- ./app:/webapp/app
- ./lib:/webapp/lib
- ./webapp-ui:/webapp/webapp-ui
depends_on:
- db
- redis
I do include certain parts of public in .dockerignore but by bind mounting the volume I think it should include it at runtime, right?
update
So, I'm not going to accept this as The Answer but I do have some more information. It seems like if I restart my computer, start a fresh docker engine, and fire it up against built static files in public/assets it works fine.
If, while it's running, I do a build that updates the static assets in public/assets, that build's output is now captured by my container. Great!
But if I start, on my filesystem, a webpack dev server? That's when everything falls apart. I suspect it's doing something to the folder on the filesystem that isn't playing nicely with Docker. What sucks, though, is that once I start that dev server even once my Docker setup is hosed until I restart my computer.
That's... really weird?