Generating files in Docker vs. Docker Compose - ruby-on-rails

I am learning Docker and have a little confusion that I would greatly appreciate some advice on.
When creating a new rails application. Following the guidelines of Docker's tutorial Quickstart: Compose & Rails. They're steps are as follows.
Create Dockerfile & Entrypoint
Create Gemfile with source and rails gem listed.
Create an empty Gemfile.lock
Create a docker-compose.yml and name services.
Generate a new rails app using docker-compose run.
The rails new command.
docker-compose run --no-deps web rails new . --force -d mysql
This is my simple Dockerfile:
FROM ruby:3.1.1-alpine3.15
RUN apk update -q
RUN apk add bash yarn git build-base nodejs mariadb-dev tzdata
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
COPY package.json /app/package.json
COPY yarn.lock /app/yarn.lock
RUN gem install bundler
RUN gem update bundler
RUN bundle install
COPY entrypoint.sh /usr/bin
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
This is my docker-compose.yml:
version: "3.9"
services:
web:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/app
ports:
- "3000:3000"
As you can see I only have one service listed in my docker-compose.yml, because I do not want any other services or containers.
Everything works fine when I use the command docker-compose run rails new ... it generates a new rails app and persists the files to my local source folder. So I can open and edit the files to develop the application. Perfect.
But when I try and generate a new Rails app without using docker-compose.yml and only the Dockerfile. The generated files are not persisted in my local source folder, only on the container. I can only assume because I am forced to build the image before running it in a container to generate the new rails app.
When using without docker-compose.yml. I have to build the image first.
docker build -t image_name .
Then I can run the container and generate the rails app.
docker run image_name rails new . --force -d mysql
Obviously, this doesn't persit the new files in my local source folder.
How can I generate a new Rails app with just my Dockerfile without docker compose and still have the newly generated files persist in my local source folder?
I must be missing something, but I've done a lot of research and can't find an answer.

In docker-compose.yml you have a 'volumes' key as seen below.
volumes:
- .:/app
But when using it in the CLI you miss passing these volumes.
You can pass these volumes as in the code below.
docker run -d --name=rails --volume /your/workdir:/app image_name
If you want to learn more about volumes, you can find out here.

Related

Rails bundle "Can't find GEM in any of the sources" in Docker Container only

I'm using Rails in a Docker container, and every once in a while I run into this issue that I have no idea how to solve. When adding a new gem to the Gemfile, upon rebuilding the Docker Image + Container, the build will fail with the common bundler error Could not find [GEM_NAME] in any of the sources; Run 'bundle install' to install missing gems. This only occurs to me when I try to build the image in Docker, if I run a regular bundle install on my local machine, the Gemfile gets installed correctly and everything works as expected.
I have a fairly standard Dockerfile & docker-compose file.
Dockerfile:
FROM ruby:2.6.3
ARG PG_MAJOR
ARG BUNDLER_VERSION
ARG UID
ARG MODE
# Add POSTGRESQL to the source list using the right version
RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
&& echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' $PG_MAJOR > /etc/apt/sources.list.d/pgdg.list
ENV RAILS_ENV $MODE
RUN apt-get update -qq && apt-get install -y postgresql-client-$PG_MAJOR vim
RUN apt-get -y install sudo
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
COPY Gemfile /usr/src/app/Gemfile
COPY Gemfile.lock /usr/src/app/Gemfile.lock
ENV BUNDLER_VERSION $BUNDLER_VERSION
RUN gem install bundler:$BUNDLER_VERSION
RUN bundle install
COPY . /usr/src/app
# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
docker-compose.yml:
version: '3'
services:
backend:
build:
dockerfile: Dockerfile
args:
UID: ${UID:-1001}
BUNDLER_VERSION: 2.0.2
PG_MAJOR: 10
mode: development
tty: true
stdin_open: true
volumes:
- ./[REDACTED]:/usr/src/app
- gem_data_api:/usr/local/bundle:cached
ports:
- "3000:3000"
user: root
I've tried docker system prune -a, docker builder prune -a, reinstalling Docker, multiple rebuilds in a row, restarting my machine and so on, to no avail. The weird part is that it doesn't happen with every new Gem that I decide to add, only for some specific gems. For example I got this issue once again when trying to add gem 'sendgrid-ruby' to my Gemfile. This is the repo for the gem for reference, and the specific error I get with sendgrid-ruby is Could not find ruby_http_client-3.5.1 in any of the sources. I tried specifying ruby_http_client in my Gemfile, and I also tried sshing into the Docker container and running gem install ruby_http_client, but I get the same errors.
What might be happening here?
You're mounting a named volume over the container's /usr/local/bundle directory. The named volume will get populated from the image, but only the very first time you run the container. After that the old contents of the named volume will take precedence over the content of the image: using a volume this way will cause Docker to completely ignore any changes you make in the Gemfile.
You should be able to delete that volumes: line from the docker-compose.yml file. I'm not clear what benefit you would get from keeping the installed gems in a named volume.

Error while creating mount source path when using docker-compose in Windows

I am trying to dockerize my React-Flask app by dockerizing each one of them and using docker-compose to put them together.
Here the Dockerfiles for each app look like:
React - Frontend
FROM node:latest
WORKDIR /frontend/
ENV PATH /frontend/node_modules/.bin:$PATH
COPY package.json /frontend/package.json
COPY . /frontend/
RUN npm install --silent
RUN npm install react-scripts#3.0.1 -g --silent
CMD ["npm", "run", "start"]
Flask - Backend
#Using ubuntu as our base
FROM ubuntu:latest
#Install commands in ubuntu, including pymongo for DB handling
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
RUN python -m pip install pymongo[srv]
#Unsure of COPY command's purpose, but WORKDIR points to /backend
COPY . /backend
WORKDIR /backend/
RUN pip install -r requirements.txt
#Run order for starting up the backend
ENTRYPOINT ["python"]
CMD ["app.py"]
Each of them works fine when I just use docker build and docker up. I've checked that they work fine when they are built and ran independently. However, when I docker-compose up the docker-compose.yml which looks like
# Docker Compose
version: '3.7'
services:
frontend:
container_name: frontend
build:
context: frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
- '.:/frontend'
- '/frontend/node_modules'
backend:
build: ./backend
ports:
- "5000:5000"
volumes:
- .:/code
Gives me the error below
Starting frontend ... error
Starting dashboard_backend_1 ...
ERROR: for frontend Cannot start service sit-frontend: error while creating mount source path '/host_mnt/c/Users/myid/DeskStarting dashboard_backend_1 ... error
ERROR: for dashboard_backend_1 Cannot start service backend: error while creating mount source path '/host_mnt/c/Users/myid/Desktop/dashboard': mkdir /host_mnt/c: file exists
ERROR: for frontend Cannot start service frontend: error while creating mount source path '/host_mnt/c/Users/myid/Desktop/dashboard': mkdir /host_mnt/c: file exists
ERROR: for backend Cannot start service backend: error while creating mount source path '/host_mnt/c/Users/myid/Desktop/dashboard': mkdir /host_mnt/c: file exists
ERROR: Encountered errors while bringing up the project.
Did this happen because I am using Windows? What can be the issue? Thanks in advance.
For me the only thing that worked was restarting the Docker deamon
Check if this is related to docker/for-win issue 1560
I had the same issue. I was able to resolve it by running:
docker volume rm -f [name of docker volume with error]
Then restarting docker, and running:
docker-compose up -d --build
I tried these same steps without restarting my docker, but restarting my computer and that didn't resolve the issue.
What resolved the issue for me was removing the volume with the error, restarting my docker, then doing a build again.
Other cause:
On Windows this may be due to a user password change. Uncheck the box to stop sharing the drive and then allow Docker to detect that you are trying to mount the drive and share it.
Also mentioned:
I just ran docker-compose down and then docker-compose up. Worked for me.
I have tried with docker container prune then press y to remove all stopped containers. This issue has gone.
I saw this after I deleted a folder I'd shared with docker and recreated one with the same name. I think this deleted the permissions. To resolve it I:
Unshared the folder in docker settings
Restarted docker
Ran docker container prune
Ran docker-compose build
Ran docker-compose up.
Restarting the docker daemon will work.

Running composer install in docker container

I have a docker-compose.yml script which looks like this:
version: '2'
services:
php:
build: ./docker/php
volumes:
- .:/var/www/website
The DockerFile located in ./docker/php looks like this:
FROM php:fpm
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer
RUN composer update -d /var/www/website
Eventho this always fails with the error
[RuntimeException]
Invalid working directory specified, /var/www/website does not exist.
When I remove the RUN composer update line and enter the container, the directory does exist and contains my project code.
Please tell me if I am doing anything wrong OR if I'm doing the composer update on a wrong place
RUN ... lines are run when the image is being built.
Volumes are attached to the container. You have at least two options here:
use COPY command to, well, copy your app code to the image so that all commands after that command will have access to it. (Do not push the image to any public Docker repo as it will contain your source that you probably don't want to leak)
install composer dependencies with command run on your container (CMD or ENTRYPOINT in Dockerfile or command option in docker-compose)
You are mounting your local volume over your build directory so anything you built in '/var/www/website' will be mounted over by your local volume when the container runs.

Serving Rails' precompiled assets using nginx in Docker

Currently I'm setting up my app using docker. I've got a minimal rails app, with 1 controller. You can get my setup by running these:
rails new app --database=sqlite --skip-bundle
cd app
rails generate controller --skip-routes Home index
echo "Rails.application.routes.draw { root 'home#index' }" > config/routes.rb
echo "gem 'foreman'" >> Gemfile
echo "web: rails server -b 0.0.0.0" > Procfile
echo "port: 3000" > .foreman
And I have the following setup:
Dockerfile:
FROM ruby:2.3
# Install dependencies
RUN apt-get update && apt-get install -y \
nodejs \
sqlite3 \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
# Configure bundle
RUN bundle config --global frozen 1
RUN bundle config --global jobs 7
# Expose ports and set entrypoint and command
EXPOSE 3000
CMD ["foreman", "start"]
# Install Gemfile in different folder to allow caching
WORKDIR /tmp
COPY ["Gemfile", "Gemfile.lock", "/tmp/"]
RUN bundle install --deployment
# Set environment
ENV RAILS_ENV production
ENV RACK_ENV production
# Add files
ENV APP_DIR /app
RUN mkdir -p $APP_DIR
COPY . $APP_DIR
WORKDIR $APP_DIR
# Compile assets
RUN rails assets:precompile
VOLUME "$APP_DIR/public"
Where VOLUME "$APP_DIR/public" is creating a volume that's shared with the Nginx container, which has this in the Dockerfile:
FROM nginx
ADD nginx.conf /etc/nginx/nginx.conf
And then docker-compose.yml:
version: '2'
services:
web:
build: config/docker/web
volumes_from:
- app
links:
- app:app
ports:
- 80:80
- 443:443
app:
build: .
environment:
SECRET_KEY_BASE: 'af3...ef0'
ports:
- 3000:3000
This works, but only the first time I build it. If I change any assets, and build the images again, they're not updated. Possibly because volumes are not updated on image build, I think because how Docker handles caching.
I want the assets to be updated every time I run docker-compose built && docker-compose up. Any idea how to accomplish this?
Compose preserves volumes on recreate.
You have a couple options:
don't use volumes for the assets, instead build the assets and ADD or COPY them into the web container during build
docker-compose rm app before running up to remove the old container and volumes.

How can I write Dockerfile for Yesod? "RUN yesod init -n myApp -d postgresql" didn't work as expected

I tried to make a simple application with Yesod and PostgreSQL using Docker Compose but RUN yesod init -n myApp -d postgresql didn't seem to work as expected.
I defined Dockerfile and docker-compose.yml as below:
Dockerfile:
FROM shuny/ghc-7.8.4:latest
MAINTAINER shuny
# Create default config
RUN cabal update
# Add stackage remote repo
RUN sed -i 's/^remote-repo: [a-zA-Z0-9_\/:.]*$/remote-repo: stackage:http:\/\/www.stackage.org\/lts/g' /root/.cabal/config
# Update packages
RUN cabal update
# Generate locale otherwise happy (because of tf-random) will fail
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
RUN echo $LANG
# Install build tools for yesod
RUN cabal install alex happy yesod-bin
# Install library for yesod-postgres
RUN apt-get update && apt-get install -y libpq-dev
RUN mkdir /code
WORKDIR /code
RUN yesod init -n myApp -d postgresql
WORKDIR /code/myApp
RUN cabal sandbox init && cabal install --only-dependencies --max-backjumps=-1 --reorder-goals
RUN cabal configure && cabal build && cabal install
ADD . /code
WORKDIR /code
# ADD settings.yml /code/myApp/config/
docker-compose.yml:
database:
image: postgres
ports:
- "5432"
web:
build: .
tty: true
command: yesod devel
volumes:
- .:/code/
ports:
- "3000:3000"
links:
- database
and docker-compose build returned as below:
Step 0 : FROM shuny/ghc-7.8.4:latest
...
Step 17 : WORKDIR /code
---> Running in bf99d0aca48c
---> 37c3c94338d7
Removing intermediate container bf99d0aca48c
Successfully built 37c3c94338d7
but when I check like this:
$docker-compose run web /bin/bash
root#0fe5fb1a3b20:/code# ls
root#0fe5fb1a3b20:/code#
it showed nothing while this commands seem to work as expected:
docker run -ti 37c3c94338d7
root#31e94428de37:/code# ls
docker-compose.yml Dockerfile myApp settings.yml
root#31e94428de37:/code# ls myApp/
app config Handler Model.hs Settings.hs test
Application.hs dist Import myApp.cabal static
cabal.sandbox.config Foundation.hs Import.hs Settings templates
How can I fix it?
I really appliciate any feedback, thank you.
You are doing strange things with volumes and the ADD instruction.
First you build your application inside the image:
RUN yesod init -n myApp -d postgresql
WORKDIR /code/myApp
RUN cabal sandbox init && cabal install --only-dependencies --max-backjumps=-1 --reorder-goals
RUN cabal configure && cabal build && cabal install
Then you add the content of the folder that contains the Dockerfile in the /code folder of the image. I guess this step is useless.
ADD . /code
Then, if you run a container without -volume option, everything works fine
docker run -ti 37c3c94338d7
But in your docker-compose.yml file, you specified a volume option that overides the /code folder in the container with the folder that contains the docker-compose.yml file on the host machine. Therefore, you don't have the content generated during the build of your image anymore.
There are two possibilities:
Don't use the volume instruction in the docker-compose.yml file
Put the content of the /code/myApp/ folder of the image inside the ./myApp folder of the host.
It depends on why you want to use the volume option in docker-compose.yml.
I don't really know what is your goal. But if what you are trying to do is to access to the files built inside the container from the host machine, maybe this should do what you are looking for:
Remove the build steps from your Dockerfile
Run a shell inside a "web" container: docker-compose run web bash
Launch the build commands
So you will have built your application while the volume was mounted and will see the files on the host machine.
Exit the shell
Launch Docker Compose normally
If you just want to be able to backup the content of the /code/myApp/ folder, maybe you should omit the path on the host machine from the volume section of docker-compose.yml.
volumes:
- /code/
And follow this section of the documentation
I hope it helps

Resources