Git repo structure with multiple docker files - node.js

What is the best way to structure a repository / project that has multiple Dockerfile for provisioning services.
e.g.
Dockerfile # build Nodejs app server
Dockerfile # build Nginx forward proxy
Dockerfile # build Redis cache server
What is the best practices and standard structure within a repository to contain this information?

You generally have one folder per Dockerfile, as:
each one can use multiple other resource files (config files, data files, ...) when doing their respective docker build -t xxx .
each one can have its own .dockerignore
My project b2d, for instance, has one Dockerfile per application:

Related

Where to place app config/logs in container

I've got a python package running in a container.
Is it best practice to install it in /opt/myapp within the container?
Should the logs go in /var/opt/myapp?
Should the config files go in /etc/opt/myapp?
Is anyone recommending writing logs and config files to /opt/myapp/var/log and /opt/myapp/config?
I notice google chrome was installed in /opt/google/chrome on my (host) system, but it didn't place any configs in /etc/opt/...
Is it best practice to install it in /opt/myapp within the container?
I place my apps in my container images in /app. So in the dockerfile I do
WORKDIR /app at the beginning
Should the logs go in /var/opt/myapp?
In container world the best practice is that your application logs go into stdout, stderr and not into files inside the container because containers are ephemeral by design and should be treated that way so when a container is stopped and deleted all of its data on its filesystem is gone.
On local docker development environment you can see the logs with docker logs and you can:
start a container named gettingstarted from the image docker/getting-started:
docker run --name gettingstarted -d -p 80:80 docker/getting-started
redirect docker logs output to a local file on the docker client (your machine from where you run the docker commands):
docker logs -f gettingstarted &> gettingstarted.log &
open http://localhost to generate some logs
read the log file with tail realtime or with any text viewer program:
tail -f gettingstarted.log
Should the config files go in /etc/opt/myapp?
Again, you can put the config files anywhere you want, I like to keep them together with my app so in the /app directory, but you should not modify the config files once the container is running. What you should do is instead pass the config variables to the container as environment variables at startup with the -e flag, for example to create MYVAR variable with MYVALUE value inside the container start it this way:
docker run --name gettingstarted -d -p 80:80 -e MYVAR='MYVALUE' docker/getting-started
exec into the container to see the variable:
docker exec -it gettingstarted sh
/ # echo $MYVAR
MYVALUE
From here it is the responsibility of your containerized app to understand these variables and translate them to actual application configurations. Some/most programming languages support reaching env vars from inside the code at runtime but if this is not an option then you can do an entrypoint.sh script that updates the config files with the values supplied through the env vars. A good example for this is the postgresql entrypoint: https://github.com/docker-library/postgres/blob/master/docker-entrypoint.sh
Is anyone recommending writing logs and config files to
/opt/myapp/var/log and /opt/myapp/config?
As you can see, it is not recommended to write logs into the filesystem of the container you would rather have a solution to save them outside of the container if you need them persisted.
If you understand and follow this mindset especially that containers are ephemeral then it will be much easier for you to transition from the local docker development to production ready kubernetes infrastructures.
Docker is Linux, so almost all of your concerns are related to the best operative system in the world: Linux
Installation folder
This will help you:
Where to install programs on Linux?
Where should I put software I compile myself?
and this: Linux File Hierarchy Structure
As a summary, in Linux you could use any folder for your apps, bearing in mind:
Don't use system folders : /bin /usr/bin /boot /proc /lib
Don't use file system folder: /media / mnt
Don't use /tmp folder because it's content is deleted on each restart
As you researched, you could imitate chrome and use /opt
You could create your own folder like /acme if there are several developers entering to the machine, so you could tell them: "No matter the machine or the application, all the custom content of our company will be in /acme". Also this help you if you are a security paranoid because will be able to guess where your application is. Any way, if the devil has access to your machine, is just a matter of time to find all.
You could use fine grained permissions to keep safe the chosen folder
Log Folder
Similar to the previous paragraph:
You could store your logs the standard /var/log/acme.log
Or create your own company standard
/acme/log/api.log
/acme/webs/web1/app.log
Config Folder
This is the key for devops.
In a traditional, ancient and manually deployments, some folders were used to store the apps configurations like:
/etc
$HOME/.acme/settings.json
But in the modern epoch and if you are using Docker, you should not store manually your settings inside of container or in the host. The best way to have just one build and deploy n times (dev, test, staging, uat, prod, etc) is using environment variables.
One build , n deploys and env variables usage are fundamental for devops and cloud applications, Check the famous https://12factor.net/
III. Config: Store config in the environment
V. Build, release, run: Strictly separate build and run stages
And also is a good practice on any language. Check this Heroku: Configuration and Config Vars
So your python app should not read or expect a file in the filesystem to load its configurations. Maybe for dev, but no for test and prod.
Your python should read its configurations from env variables
import os
print(os.environ['DATABASE_PASSWORD'])
And then inject these values at runtime:
docker run -it -p 8080:80 -e DATABASE_PASSWORD=changeme my_python_app
And in your developer localhost,
export DATABASE_PASSWORD=changeme
python myapp.py
Before the run of your application and in the same shell
Config of a lot pf apps
The previous approach is an option for a couple of apps. But if you are driven to microservices and microfrontends, you will have dozens of apps on several languages. So in this case, to centralize the configurations you could use:
spring cloud
zookeeper
https://www.vaultproject.io/
https://www.doppler.com/
Or the Configurator (I'm the author)

Is it a good practice to keep nodejs server and react frontend in same directory?

I need to serve my React built files (build directory) using nodejs server. With React being wrapped in Docker my nodejs server can not access build directory within /frontend. So what I am thinking of is to move my server.js into /frontend and having a single Dockerfile for both of them.
It would have something like this CMD ['npm run build', 'node server.js']
Would that be illegal and bad practice ?
Modern stack monoliths
If your are developing a single site with its backend, it could work to have frontend and backend in the same repository or directory. It will be like the modern monoliths: mean, mern, mevn with some challenges related to the fact to have different application types in one repository.
Spa with several apis
But, if your site will be a complex spa with several menus, modules, roles, invocation to several rest apis, etc I advice you a distributed or decoupled architecture. I mean one app or process by repository/server. Here some advantages :
frontend (spa) with react
own domain like acme.com
you could use specialized services for static webs
you are not bound to only one api. You could consume several apis.
react developers could have its own git strategy
custom build for webs
backend (api) with nodejs
own domain like acme.api.com
you could scale only the backend because the heavy process is in this layer
nodejs developers could have its own git strategy
serve the spa
If your web is simple, you could use
https://www.npmjs.com/package/http-server
https://www.npmjs.com/package/serve
https://www.npmjs.com/package/static-server
But if your web has complexities like backend: env variables portability, one build, etc you could use:
https://github.com/usil/nodeboot-spa-server
References
spa with several apis image: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/architect-microservice-container-applications/direct-client-to-microservice-communication-versus-the-api-gateway-pattern
modern stack monoliths image: https://lzomedia.com/blog/different-ways-to-connect-react-frontend-and-node-backend/
You can't (*) run multiple servers in a single container.
For your setup, though, you don't need multiple servers. You can compile the React application to static files and serve those from your other application. There are some more advanced approaches where a server injects the data or a rendered copy of the page as it serves these up; these won't necessarily work with a React dev server in a separate container (probably some of the setup described in #JRichardsz's answer goes into this more).
If both halves are in the same repository, you can potentially use a Docker multi-stage build to compile the front-end application to static files, then copy the result into the main server image. This could look like:
# Build the frontend.
FROM node:lts AS frontend
WORKDIR /app
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm build
# Built files are left in /app/build; this stage has no CMD.
# Build the main server.
FROM node:lts
WORKDIR /app
COPY server/package*.json ./
RUN npm ci
COPY server/ ./
# Copy the build tree from the frontend image into this one.
COPY --from=frontend /app/build ./static
RUN npm build
# Normal metadata to set up and run the server container.
EXPOSE 3000
CMD ["npm", "run", "start"]
(*) It's technically possible, but you need to install some sort of process manager, which adds significant complication. It's less of a concern with the approach I describe here but you also lose some things like the ability to see just a single process's logs or the ability to update only one part of the system without restarting the rest of it. The CMD you suggest won't do it. I'd almost always use multiple containers over trying to shoehorn in something like supervisord.

How to use a docker image to generate static files to serve from nginx image?

I'm either missing something really obvious or I'm approaching this totally the wrong way, either way I could use some fresh insights.
I have the following docker images (simplified) that I link together using docker-compose:
frontend (a Vue.js app)
backend (Django app)
nginx
postgres
In development, I don't use nginx but instead the Vue.js app runs as a watcher with yarn serve and Django uses manage.py runserver.
What I would like to do for production (in CI/CD):
build and push backend image
build and push nginx image
build the frontend image with yarn build command
get the generated files in the nginx container (through a volume?)
deploy the new images
The problem is: if I put yarn build as the CMD in the Dockerfile, the compilation happens when the container is started, and I want it to be done in the build step in CI/CD.
But if I put RUN yarn build in the image, what do I put as CMD? And how do I get the generated static files to nginx?
The solutions I could find use multistage builds for the frontend that have an nginx image as the last step, combining the two. But I need the nginx image to be independent of the frontend image, so that doesn't work for me.
I feel like this is a problem that has been solved by many, yet I cannot find an example. Suggestions are much appreciated!
Create a volume in your docker-compose.yml file and mount the same volume to both your frontend container (to a path where the built files are, like dist folder) and nginx container (to your web root path). This way both containers will have the access to same volume.
And also, keep your yarn build as RUN command.
EDIT:
Containers need to run a program or command in order to be classified as a container, and so they could be started, stopped etc. That is by design.
If you are not planning to serve from frontend container using a command, then you should either remove it as a service (since its not) from docker-compose.yml and add it as a build stage in your nginx dockerfile, or you could use some kind of command that will run indefinitely in your frontend container, for example tail -f index.html. The first solution is a better practice.
In your nginx dockerfile add frontend build dockerfile as a first build stage:
From: node as frontend-build
WORKDIR /app
RUN yarn build
From:nginx
COPY --from=frontend-build /app/dist /usr/shared/nginx/html
...
CMD ["nginx"]

How to use Traefik PathPrefix on an Nginx Docker image?

I am making a React front-end application, and my aim is to serve the final (npm run build) version using a Docker image, which contains a multi-stage build with my the source code and Nginx. There multiple other Dockerized services involved, and Traefik acts as the load balancer / forwarder / whatever else Traefik does.
Now here's the thing: When I build the image it works fine as long as Traefik is not involved and I'm simply forwarding the application to a port in my docker-compose.yml file as follows:
version: '2.4'
services:
ui:
ports:
- someport:80
By doing this, I find the application served at http://address:someport no problem.
Now what I eventually have to do is give the application a PathPrefix, and Traefik is a good tool for this. Say I want to eventually find the application from http://address/app.
From what I understand, this is easily done in the docker-compose.yml file using labels, like:
version: '2.4'
services:
ui:
ports:
- 80
labels:
- traefik.enable=true
- traefik.http.routers.myapp.rule=PathPrefix("/app")
- traefik.http.routers.myapp.entrypoints=web
Please notify me if these labels are insufficient for what I want to achieve!
So after launching my application and navigating to http://address/app, nothing is rendered. Important! -> By looking at the console, it seems that the application is trying to access http://address/static/css... and http://address/static/js... source files and does not find them. There is nothing behind these addresses, but by adding /app in between like http://address/app/static..., the source files would be found like the are supposed to. How do I get my application to do this?
Is this a Traefik issue or an Nginx issue, or perhaps related to npm, which I find unlikely?
The Dockerfile for the as somewhat like:
# build env
FROM node:13.12.0-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package*.json ./
RUN npm ci
COPY . ./
RUN npm run build
# production env
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
I think what you're missing is that you need to let React know that it is not hosted at the root / but a sub-path. In order to configure a different path and thus enabling React to properly build the paths to also access the assets such as CSS and JS, you need to set the "homepage" key in your package.json to a different value, for example,
"homepage": "http://address/app",
You can read more on the topic in the deployment section of the React docs.
This is an interesting problem we had a couple days ago as well. I'll share what I found in the hopes that it points you in some form of right direction.
Using PathPrefix only, the application has to fully listen on the prefix.
From https://docs.traefik.io/routing/routers/#rule
Since the path is forwarded as-is, your service is expected to listen on /products.
There is a Middleware that strips out prefixes should your app not listen on the subpath:
https://docs.traefik.io/middlewares/stripprefix/
However the caveat there is
If your backend is serving assets (e.g., images or Javascript files), chances are it must return properly constructed relative URLs.
We haven't quite found solutions yet, other than changing the paths in our applications. But maaaybe this helps
For example in Next.js, I had to create next.config.js with content:
{
module.exports = {
basePath: '/nextjs',
}
where nextjs is my PathPrefix value from traefik.

How to sync back files from a Docker container to its host?

Maybe I am overcomplicating this.
My goal is to create a Docker-based workflow on Windows for Node.js application development.
In development time, I'd be able to run my app locally inside a container and still see the latest version without too much hassle (I don't want to rebuild the image everytime to see the latest).
On the other hand, when I deploy to production, I want to have my source files "baked" into the container image with all the dependencies (npm install)
So I created two Vagrantfiles - one for the container and one for its host. Here's an extract of the latter:
Vagrant.configure(2) do |config|
config.vm.provider "docker" do |docker|
docker.vagrant_vagrantfile = "host/Vagrantfile" # it references the host Vagrantfile
docker.build_dir = "." # we have a Dockerfile in the same dir
docker.create_args = ['--volume="/usr/src/host:/usr/src/appcontainer:rw"']
end
end
/usr/src/host is a directory which contains all of my source code (without node_modules). During the build of the Dockerfile, Docker copies the package.json to /usr/src/appcontainer and issues an npm install there which is fine for my second requirement (deploy to production)
But my first requirement was to change the source during development, so I mounted /usr/src/appcontainer as a volume pointing to the host directory /usr/src/host. However, this is not working, because /usr/src/host doesn't have a node_modules folder - this folder only exists in the container.
This whole problem seems to be easy - changing a file under Windows, see its changing both under the Linux host VM and in its container and vica versa... But I've got a bit stuck.
What is the best practice to achieve this syncing behaviour?
However, this is not working, because /usr/src/host doesn't have a node_modules folder
You can use one of the approaches described in this question, for instance by using a data volume dedicated for node_modules.
Or mounting the host node_modules as a separate folder within the container.

Resources