Task Overview
Often a single application consists of several microservices. It can be microservices built using different technologies and programming languages. E.g., a Yii application which has logic application and worker application. The common practice is to place Dockerfiles into separate directories. So, with Dockerfile, you can’t describe all components of the application in one file. As you need to describe image configuration in separate files, you can’t share a part of configuration between images.
werf allows describing all images of a project in a one config. This approach gives you more convenience.
In this article, we will build an example application — AtSea Shop, to demonstrate how to describe multiple images in a one config.
Requirements
- Installed werf dependencies on the host system.
- Installed multiwerf on the host system.
Select werf version
This command should be run prior running any werf command in your shell session:
. $(multiwerf use 1.1 stable --as-file)
Building the application
The example application is the AtSea Shop Demonstration Application from the Official Docker Samples repository. The application is a prototype of a small shop application consisting of several components.
It’s frontend written in React and backend written in Java Spring Boot. There will be nginx reverse proxy and payment gateway added in the project to make it more real.
Application components
Backend
It is the app
image. The backend container handles HTTP requests from the frontend container. The source code of the application is in the /app
directory. It consists of Java application and ReactJS application. To build the backend image there are two artifact images (read more about artifacts here) - storefront
and appserver
.
Image of the backend base on the official java image. It uses files from artifacts and doesn’t need any steps for downloading packages or building.
image: app
from: java:8-jdk-alpine
docker:
ENTRYPOINT: ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"]
CMD: ["--spring.profiles.active=postgres"]
shell:
beforeInstall:
- mkdir /app
- adduser -Dh /home/gordon gordon
import:
- artifact: storefront
add: /usr/src/atsea/app/react-app/build
to: /static
after: install
- artifact: appserver
add: /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar
to: /app
after: install
Storefront artifact
Builds assets. After building werf imports assets into the /static
directory of the app
image. To increase the efficiency of the building storefront
image, build instructions divided into two stages — install and setup.
artifact: storefront
from: node:12.10-alpine
git:
- add: /app/react-app
to: /usr/src/atsea/app/react-app
stageDependencies:
install:
- package.json
setup:
- src
- public
shell:
install:
- cd /usr/src/atsea/app/react-app
- npm install
setup:
- cd /usr/src/atsea/app/react-app
- npm run build
Appserver artifact
Builds a Java code. werf imports the resulting jarfile AtSea-0.0.1-SNAPSHOT.jar
into the /app
directory of the app
image. To increase the efficiency of the building appserver
image, build instructions divided into two stages — install and setup. Also, the /usr/share/maven/ref/repository
directory mounts with the build_dir
directives to allow some caching (read more about mount directives here).
artifact: appserver
from: maven:3.6.2-jdk-8
mount:
- from: build_dir
to: /usr/share/maven/ref/repository
git:
- add: /app
to: /usr/src/atsea
stageDependencies:
install:
- pom.xml
setup:
- src
shell:
install:
- cd /usr/src/atsea
- mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:go-offline
setup:
- cd /usr/src/atsea
- mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests
Frontend
It is the reverse_proxy
image. This image base on the official image of the NGINX server. It acts as a frontend and is configured as a reverse proxy. The frontend container handles all incoming traffic, cache it and pass requests to the backend container.
image: reverse_proxy
from: nginx:1.17-alpine
ansible:
install:
- name: "Copy nginx.conf"
copy:
content: |
{{ .Files.Get "reverse_proxy/nginx.conf" | indent 8 }}
dest: /etc/nginx/nginx.conf
- name: "Copy SSL certificates"
file:
path: /run/secrets
state: directory
owner: nginx
- copy:
content: |
{{ .Files.Get "reverse_proxy/certs/revprox_cert" | indent 8 }}
dest: /run/secrets/revprox_cert
- copy:
content: |
{{ .Files.Get "reverse_proxy/certs/revprox_key" | indent 8 }}
dest: /run/secrets/revprox_key
Database
It is the database
image. This image base on the official image of the PostgreSQL server. werf adds configs and SQL file for bootstrap in this image. The backend container uses the database to store its data.
image: database
from: postgres:11
docker:
ENV:
POSTGRES_USER: gordonuser
POSTGRES_DB: atsea
ansible:
install:
- raw: mkdir -p /images/
- name: "Copy DB configs"
copy:
content: |
{{ .Files.Get "database/pg_hba.conf" | indent 8 }}
dest: /usr/share/postgresql/11/pg_hba.conf
- copy:
content: |
{{ .Files.Get "database/postgresql.conf" | indent 8 }}
dest: /usr/share/postgresql/11/postgresql.conf
git:
- add: /database/docker-entrypoint-initdb.d/
to: /docker-entrypoint-initdb.d/
Payment gateway
It is the payment_gw
image. This image is an example of the payment gateway application. It does nothing except infinitely writes messages to stdout. Payment gateway acts as another component of the application.
image: payment_gw
from: alpine:3.9
docker:
CMD: ["/home/payment/process.sh"]
ansible:
beforeInstall:
- name: "Create payment user"
user:
name: payment
comment: "Payment user"
shell: /bin/sh
home: /home/payment
- file:
path: /run/secrets
state: directory
owner: payment
- copy:
content: |
production
dest: /run/secrets/payment_token
git:
- add: /payment_gateway/process.sh
to: /home/payment/process.sh
owner: payment
Step 1: Clone the application repository
Clone the AtSea Shop repository:
git clone https://github.com/dockersamples/atsea-sample-shop-app.git
Step 2: Create a config
To build an application with all of its components create the following werf.yaml
in the root folder of the repository:
project: atsea-shop
configVersion: 1
---
artifact: storefront
from: node:12.10-alpine
git:
- add: /app/react-app
to: /usr/src/atsea/app/react-app
stageDependencies:
install:
- package.json
setup:
- src
- public
shell:
install:
- cd /usr/src/atsea/app/react-app
- npm install
setup:
- cd /usr/src/atsea/app/react-app
- npm run build
---
artifact: appserver
from: maven:3.6.2-jdk-8
mount:
- from: build_dir
to: /usr/share/maven/ref/repository
git:
- add: /app
to: /usr/src/atsea
stageDependencies:
install:
- pom.xml
setup:
- src
shell:
install:
- cd /usr/src/atsea
- mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:go-offline
setup:
- cd /usr/src/atsea
- mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests
---
image: app
from: java:8-jdk-alpine
docker:
ENTRYPOINT: ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"]
CMD: ["--spring.profiles.active=postgres"]
shell:
beforeInstall:
- mkdir /app
- adduser -Dh /home/gordon gordon
import:
- artifact: storefront
add: /usr/src/atsea/app/react-app/build
to: /static
after: install
- artifact: appserver
add: /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar
to: /app
after: install
---
image: reverse_proxy
from: nginx:1.17-alpine
ansible:
install:
- name: "Copy nginx.conf"
copy:
content: |
{{ .Files.Get "reverse_proxy/nginx.conf" | indent 8 }}
dest: /etc/nginx/nginx.conf
- name: "Copy SSL certificates"
file:
path: /run/secrets
state: directory
owner: nginx
- copy:
content: |
{{ .Files.Get "reverse_proxy/certs/revprox_cert" | indent 8 }}
dest: /run/secrets/revprox_cert
- copy:
content: |
{{ .Files.Get "reverse_proxy/certs/revprox_key" | indent 8 }}
dest: /run/secrets/revprox_key
---
image: database
from: postgres:11
docker:
ENV:
POSTGRES_USER: gordonuser
POSTGRES_DB: atsea
ansible:
install:
- raw: mkdir -p /images/
- name: "Copy DB configs"
copy:
content: |
{{ .Files.Get "database/pg_hba.conf" | indent 8 }}
dest: /usr/share/postgresql/11/pg_hba.conf
- copy:
content: |
{{ .Files.Get "database/postgresql.conf" | indent 8 }}
dest: /usr/share/postgresql/11/postgresql.conf
git:
- add: /database/docker-entrypoint-initdb.d/
to: /docker-entrypoint-initdb.d/
---
image: payment_gw
from: alpine:3.9
docker:
CMD: ["/home/payment/process.sh"]
ansible:
beforeInstall:
- name: "Install shadow utils"
package:
name: shadow
state: present
- name: "Create payment user"
user:
name: payment
comment: "Payment user"
shell: /bin/sh
home: /home/payment
- file:
path: /run/secrets
state: directory
owner: payment
- copy:
content: |
production
dest: /run/secrets/payment_token
git:
- add: /payment_gateway/process.sh
to: /home/payment/process.sh
owner: payment
Step 3: Create SSL certificates
The NGINX in the reverse_proxy
image listen on SSL ports and need a key and certificate.
Execute the following command in the root folder of the project to create them:
mkdir -p reverse_proxy/certs && openssl req -newkey rsa:4096 -nodes -subj "/CN=atseashop.com;" -sha256 -keyout reverse_proxy/certs/revprox_key -x509 -days 365 -out reverse_proxy/certs/revprox_cert
Step 4: Build images
Execute the following command in the root folder of the project to build all images:
werf build --stages-storage :local
Step 5: Modify /etc/hosts file
To have an ability to open the example by the http://atseashop.com
URL, add the atseashop.com
name pointing to the address of your local interface into your /etc/hosts
file. E.g.:
sudo sed -ri 's/^(127.0.0.1)(\s)+/\1\2atseashop.com /' /etc/hosts
Step 6: Run the application
To run the application images, execute the following commands from the root folder of the project:
werf run --stages-storage :local --docker-options="-d --name payment_gw" payment_gw &&
werf run --stages-storage :local --docker-options="-d --name database -p 5432:5432" database &&
werf run --stages-storage :local --docker-options="-d --name app -p 8080:8080 --link database:database" app &&
werf run --stages-storage :local --docker-options="-d --name reverse_proxy -p 80:80 -p 443:443 --link app:appserver" reverse_proxy
Check that all containers are running, by executing:
docker ps
You should see running containers with names: reverse_proxy, app, database, payment_gw and registry.
Wait for about 30 seconds or a bit more for all the containers to be ready, then open the atseashop.com in your browser. You will be redirected by NGINX to https://atseashop.com
and you will get a warning message from your browser about the security of the connection, because there is a self-signed certificate used in the example. You need to add an exception to open the https://atseashop.com
page.
Stopping the application
To stop application containers execute the following command:
docker stop reverse_proxy app database payment_gw
Conclusions
We’ve described all project images in a one config.
The example above shows the benefits:
- If your project has similar images, you can share some piece of images by mounting their folder with the
build_dir
directive (read more about mounts here). - You can share artifacts between images in single config.
- Common templates can be used in single config to describe configuration of multiple images.