Overview
Goals and plan
This guide demonstrates how to develop new modules for Add-On Mart and prepare them for deployment using the CLI tool `addon-mart`. With this tool, you can efficiently manage development and debugging in a local deployment environment.
By separating configuration into a module schema (defining options) and storing the actual values in separate files, you can easily configure and manage the application. Built-in support for templates, file mounting, and Docker commands allows you to configure, build, and run the application locally with a single command.
This document walks you through pre-built service components, which follow a typical microservices architecture: frontend, backend, and database. Our example is a portal application that provides access to pages and features for authorized users, including login and self-registration pages.
The demo application serves as a tutorial foundation and can be expanded and enhanced as needed. The frontend is built on Angular (using TypeScript and an HTTP web server), while the backend is based on FastAPI (using Python and an ASGI web server). Each service component is Dockerized and can be deployed on any platform with any orchestrator. However, the main goal is to prepare the application for placement in our marketplace and get it ready for deployment in a test environment.
The first sections guide you through launching a single frontend container with Dockerfile, explaining the structure, passing parameters through environment variables, file organization, and the launch command. The next sections assist in creating the final set of configuration files needed to launch the complete application.
For those looking to use the solution as is and skip intermediate steps, you can download the final file set from the last step.
Component diagram
Name | Description |
---|---|
Frontend | The frontend is the part of a web application that interacts directly with the user. It is responsible for displaying data and facilitating user interactions, providing a convenient interface and visual representation of the application's functions. The primary purpose of the frontend is to create an intuitive and engaging user experience. |
Backend | The backend is the part of a web application that operates on the server and processes data. It is responsible for storing, processing, and transmitting data between the server and the frontend, providing the application’s logic and functionality. The primary purpose of the backend is to support and manage the server-side operations, ensuring reliability and security. |
MongoDB | MongoDB is a NoSQL database used for storing and managing data in an unstructured format, such as JSON documents, for instance, list of customers |
Mongo Express | Mongo Express is a web-based interface for managing MongoDB databases. It allows users to view, add, edit, and delete documents, as well as run queries on data through a user-friendly graphical interface. The primary purpose of Mongo Express is to simplify working with MongoDB by providing an intuitive way to interact with the database. This solution is used only for the development environment to test the application and provide convenient access to the data in the database. In a production environment, this service should not be deployed for security reasons. |
Frontend
Pre-authorization | Authorized access |
---|---|
Login pageUse login name `admin` and password `admin` for authorization by default. | Home page |
Register page | Users pageIt is used for managing the users (List/Add/Edit/Delete). |
Backend
FastAPI provides built-in visual tools for displaying the OpenAPI schema, making it easier to interact with and understand the API. These tools are available by default on the backend, streamlining the development process and improving accessibility for developers.
The current API description based on OpenAPI v3 JSON:
Link to openapi.json for downloading and link to https://editor.swagger.io/ where you should see the visual
After starting the application, API documentation will be available via Swagger (http://localhost:5500/docs) and Redoc (http://localhost:5500/redoc).
Swager UI | Redoc UI |
---|---|
Mongo Express
Installing addon-mart tool
Follow this link and installation instructions to have addon-mart tool installed. After this, configure it to work locally with command:
$ addon-mart set-context local
You can find more information here.
Introduction to the configuration schema
By defining a separate configuration schema, you store the settings and options outside of the application code. The schema includes metadata that specifies validation types, mandatory fields, and default values, helping ensure correct configurations.
The image below shows an example of the configuration schema on the left, while on the right is the Add-On Mart web interface for configuration, which is dynamically generated from the schema into an interactive form.
This metadata also allows the application to automatically generate a dynamic HTML form based on the configuration schema, enabling users to input values through a clear and user-friendly interface.
For more information on the structure and fields of the module schema, please refer to the detailed documentation or online with schema-generated UI
Launching a single-container
The initial pack of the Frontend application code you can find here. Clone it to your local computer to continue with this tutorial
Building a schema for a frontend application
By examining the src/assets/env.template.js
listing, we can determine the environment variables that can be configured.
(function (window) { window['env'] = window['env'] || {}; window['env']['apiUrl'] = '${API_URL}'; })(this);
This example shows where the variable is used for an Angular app. If you are using your own app, the set of variables will already be known to you.
So, we have just one environment variable. Let's create addonmart-tool/config_schema.json
file based on an online schema-generated UI tool or manually
{ "groups": [ { "name": "Frontend", "provider": "APP_OWNER", "settings": [ { "id": "api_url", "label": "API URL", "type": "text", "mandatory": true, "info": "URL for the API endpoint that the frontend will interact with.", "placeholder": "https://api.example.com", "default": null } ] } ] }
The definition of the field names and description can be found in the documentation.
The value of the id
field should be:
- required;
- unique for all schema;
- in the snake_case style as to the Naming policy.
Create a file with the value of the variables
Let's create addonmart-tool/config_values.yaml
file with just one api_url option
options: api_url: http://localhost:5500
Pay attention to following the Naming policy. Please note that this is a temporary value for local testing. In production or staging mode, this value will be retrieved from the configuration server.
Naming policy
- Use the snake_case style for the option name in the config schema file ('id' field)
- Use the snake_case style for the config values file
- The tool generates environment variables in the UPPERCASE_WITH_UNDERSCORES style
- Use the camelCase style as a placeholder name in template files
First launch
Overview of the current file structure of the frontend folder (just 2 levels):
$ tree -L 2 . ├── addonmart-tool │ ├── config_schema.json │ └── config_values.yaml ├── angular.json ├── Dockerfile ├── nginx.conf ├── package.json ├── package-lock.json ├── scripts │ └── init_container.sh ├── src │ ├── app │ ├── assets │ ├── environments │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ └── styles.css ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json
Use run
command to launch the frontend application
$ addon-mart run \ --configuration-schema ./addonmart-tool/config_schema.json \ --local-values ./addonmart-tool/config_values.yaml \ --build ./addonmart-tool/build \ --app-dir ./ \ --app-name aom-web-app:1.0.1 \ --port 80:80
INFO: > validating configuration values... INFO: reading ./addonmart-tool/config_schema.json file... INFO: reading ./addonmart-tool/config_values.yaml file... INFO: creating ./addonmart-tool/build/config_values.env file... SUCCESS: configuration values are valid. INFO: building "aom-web-app:1.0.1" image using Dockerfile... {"stream":"Step 1/12 : FROM node:18-slim AS build"} {"stream":"\n"} ... {"stream":" ---\u003e Using cache\n"} {"stream":" ---\u003e eed0c7a31967\n"} {"stream":"Step 12/12 : CMD [\"sh\", \"init_container.sh\"]"} {"stream":"\n"} {"stream":" ---\u003e Using cache\n"} {"stream":" ---\u003e 1a805c528dab\n"} {"aux":{"ID":"sha256:1a805c528dab5c7541c7e12a2d1b54ad741073ce0289017c72225087c55ac4cd"}} {"stream":"Successfully built 1a805c528dab\n"} {"stream":"Successfully tagged aom-web-app:1.0.1\n"} INFO: creating "aom-web-app" container... INFO: starting "aom-web-app" container... SUCCESS: "aom-web-app" container has been successfully started. INFO: please use "docker logs -f aom-web-app" to see logs. INFO: please use "docker container stop aom-web-app" to stop the container.
Use help to get the description for each argument
$ addon-mart run -h NAME: addon-mart run - Allows to build and start a dockerized application with provided configuration USAGE: addon-mart run [command options] [arguments...] OPTIONS: --configuration-schema value Path to a file with application configuration-schema. Note: this file should be in JSON (.json) format (required) --local-values value Path to a file with configuration values. Note: this file should be in YAML (.yaml) format (default: "config_values.yaml") --remote-values value Combination of instance ID and access token for connection with configuration server. Format: <instance_id>:<access_token> --build value Path to a directory for processed configuration values files and templates (default: "build/") --files-mapping value Path to the file with mapping of external files. Note: this file should be in YAML (.yaml) format --app-name value Name of the app (it is used to name an image and container) --port value Port mapping for app container. Format <host_port>:<container_port> --app-dir value Path to a directory with source code of the app. Note: this directory should contain Dockerfile file --compose-config value Name of compose configuration file --templates-mapping value Path to the file with mapping templates. Note: this file should be in YAML (.yaml) format --templates value Path to a directory with templates --help, -h show help
Check the running web application on the localhost: http://localhost/
Check the status of the image, container
$ docker images aom-web-app REPOSITORY TAG IMAGE ID CREATED SIZE aom-web-app 1.0.1 1a805c528dab 27 hours ago 12.3MB $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9223239c787b aom-web-app:1.0.1 "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:80->80/tcp aom-web-app
Let's check the environment variables passed to the running container
$ docker exec aom-web-app env | sort API_URL=http://localhost:5500 HOME=/root HOSTNAME=9223239c787b NGINX_VERSION=1.25.4 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PKG_RELEASE=1
So, the environment variable API_URL
and its value is passed successfully
Checking logs
Use docker logs
command to check the container log ("-f" argument helps to follow new messages in the realtime)
$ docker logs -f aom-web-app 2024/11/07 15:18:02 [notice] 8#8: using the "epoll" event method 2024/11/07 15:18:02 [notice] 8#8: nginx/1.25.4 2024/11/07 15:18:02 [notice] 8#8: built by gcc 12.2.1 20220924 (Alpine 12.2.1_git20220924-r10) 2024/11/07 15:18:02 [notice] 8#8: OS: Linux 6.8.0-48-generic 2024/11/07 15:18:02 [notice] 8#8: getrlimit(RLIMIT_NOFILE): 1048576:1048576 2024/11/07 15:18:02 [notice] 8#8: start worker processes 2024/11/07 15:18:02 [notice] 8#8: start worker process 9 ... 2024/11/07 15:18:02 [notice] 8#8: start worker process 19 2024/11/07 15:18:02 [notice] 8#8: start worker process 20 172.17.0.1 - - [07/Nov/2024:15:27:07 +0000] "GET /account/login?returnUrl=%2F HTTP/1.1" 200 675 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" "-" 172.17.0.1 - - [07/Nov/2024:15:27:07 +0000] "GET /assets/env.js HTTP/1.1" 200 128 "http://localhost/account/login?returnUrl=%2F" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" "-" 172.17.0.1 - - [07/Nov/2024:15:27:07 +0000] "GET /favicon.ico HTTP/1.1" 200 15086 "http://localhost/account/login?returnUrl=%2F" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" "-"
Stopping the container
Use docker container stop
command to stop the container
$ docker container stop aom-web-app aom-web-app
Auto-generated build folder
A build folder has been created with the contents that will be used to launch the container. Do not edit the files here manually, they will be automatically overwritten at the next launch.
$ tree addonmart-tool/ addonmart-tool/ ├── build │ └── config_values.env ├── config_schema.json └── config_values.yaml
Listing the generated environment variable file
API_URL="http://localhost:5500"
Binding of custom static files
You can use file binding as a configuration option to customize or brand the application. For example, this tool allows you to replace the favicon.ico
file used as the browser icon. Let's create a directory "assets" and find and copy any random favicon.ico to replace the icon we have by default.
$ tree addonmart-tool/ addonmart-tool/ ├── assets │ └── favicon.ico ├── build │ └── config_values.env ├── config_schema.json ├── config_values.yaml └── files_mapping.yaml
The path to the new favicon file is addonmart-tool/assets/favicon.ico
.
Let's create a file with source mapping (absolute path) and target mount point in the container (absolute path)
mapping: - <absolute_path_to_file>/frontend/addonmart-tool/assets/favicon.ico:/usr/share/nginx/html/favicon.ico
Execute the launch command
$ addon-mart run \ --configuration-schema ./addonmart-tool/config_schema.json \ --local-values ./addonmart-tool/config_values.yaml \ --files-mapping ./addonmart-tool/files_mapping.yaml \ --build ./addonmart-tool/build \ --app-dir ./ \ --app-name aom-web-app:1.0.1 \ --port 80:80
Notice, the new --files-mapping ./addonmart-tool/files_mapping.yaml
argument
The browser may cache the page icon file, so a hard reload of the page may be required.
The final version of the Frontend application code is available for download via this link.
Launch the entire application at once
Overview
The initial pack of the application code you can find here
Overview of the current file structure of the application folder (just 3 levels):
$ tree -L 3 -a . ├── backend │ ├── app │ │ └── main.py │ ├── Dockerfile │ └── requirements.txt ├── compose.yaml ├── .env.example ├── frontend │ ├── angular.json │ ├── Dockerfile │ ├── nginx.conf │ ├── package.json │ ├── package-lock.json │ ├── scripts │ │ └── init_container.sh │ ├── src │ │ ├── app │ │ ├── assets │ │ ├── environments │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ └── styles.css │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json └── mongo └── init-mongo.js.example
We have separate folders for each component. Let's look Docker Compose file:
version: '3.3' services: web: build: context: './frontend' ports: - '80:80' env_file: '.env.example' depends_on: - back networks: - portal-network back: build: context: './backend' target: builder ports: - '5500:80' env_file: '.env.example' depends_on: - mongo networks: - portal-network mongo: container_name: mongo image: mongo ports: - 27017:27017 env_file: '.env.example' volumes: - mongo_data:/data/db - ./mongo/init-mongo.js.example:/docker-entrypoint-initdb.d/init-mongo.js networks: - portal-network mongo-express: image: mongo-express ports: - 8081:8081 env_file: '.env.example' depends_on: - mongo networks: - portal-network networks: portal-network: driver: bridge volumes: mongo_data:
You can find the official definition and description in the Docker Compose documentation: https://docs.docker.com/compose/.
Building a schema for the application
Every dockerized application should have a set of parameters that configure it, without which the application will not be able to start or function. This list can be found either in the README file or (as in our case) in the example file. This file specifies which variables will be configured and we can see the data type and examples of their values.
Please review an example of the environment variable file. Here, we can find all the configuration parameters we need to build the application configuration schema.
# Frontend API_URL=http://localhost:5500 # Backend BACKEND_MONGO_USERNAME=mongo_user BACKEND_MONGO_PASSWORD=password BACKEND_MONGO_HOST=mongo BACKEND_MONGO_PORT=27017 BACKEND_MONGO_DATABASE=portal # MongoDB MONGO_INITDB_ROOT_USERNAME=mongo_root MONGO_INITDB_ROOT_PASSWORD=example MONGO_INITDB_DATABASE=portal # Mongo Express ME_CONFIG_MONGODB_ADMINUSERNAME=mongo_root ME_CONFIG_MONGODB_ADMINPASSWORD=example ME_CONFIG_MONGODB_URL=mongodb://mongo_root:example@mongo:27017/ ME_CONFIG_BASICAUTH=false
Let's create addonmart-tool/config_schema.json
file with help of an online schema-generated UI tool or manually
{ "groups":[ { "name":"Frontend", "provider":"APP_OWNER", "settings":[ { "id":"api_url", "label":"API URL", "type":"text", "mandatory":true, "info":"URL for the API endpoint that the frontend will interact with.", "placeholder":"https://api.example.com", "default":null } ] }, { "name":"Backend", "provider":"APP_OWNER", "settings":[ { "id":"backend_mongo_username", "label":"MongoDB Username", "type":"text", "mandatory":true, "info":"Username for authenticating with MongoDB in the backend.", "placeholder":"mongo_user", "default":null }, { "id":"backend_mongo_password", "label":"MongoDB Password", "type":"password", "mandatory":true, "info":"Password for MongoDB backend authentication.", "placeholder":"Enter your password", "default":null }, { "id":"backend_mongo_host", "label":"MongoDB Host", "type":"text", "mandatory":true, "info":"Host address for the MongoDB instance used by the backend.", "placeholder":"localhost", "default":null }, { "id":"backend_mongo_port", "label":"MongoDB Port", "type":"integer", "mandatory":true, "info":"Port number for MongoDB connection in the backend.", "placeholder":"27017", "default":null }, { "id":"backend_mongo_database", "label":"MongoDB Database", "type":"text", "mandatory":true, "info":"Database name for MongoDB in the backend.", "placeholder":"portal", "default":null } ] }, { "name":"MongoDB", "provider":"APP_OWNER", "settings":[ { "id":"mongo_initdb_root_username", "label":"Root Username", "type":"text", "mandatory":true, "info":"Root username for initializing MongoDB.", "placeholder":"mongo_root", "default":null }, { "id":"mongo_initdb_root_password", "label":"Root Password", "type":"password", "mandatory":true, "info":"Password for MongoDB root user during initialization.", "placeholder":"Enter root password", "default":null }, { "id":"mongo_initdb_database", "label":"MongoDB Database", "type":"text", "mandatory":true, "info":"Database to be created during MongoDB initialization.", "placeholder":"portal", "default":null }, { "id":"mongo_username", "label":"MongoDB Username", "type":"text", "mandatory":true, "info":"Username for MongoDB user access.", "placeholder":"mongo_user", "default":null }, { "id":"mongo_password", "label":"MongoDB Password", "type":"password", "mandatory":true, "info":"Password for MongoDB user access.", "placeholder":"Enter password", "default":null } ] }, { "name":"Mongo Express", "provider":"APP_OWNER", "settings":[ { "id":"me_config_mongodb_adminusername", "label":"Mongo Express Admin Username", "type":"text", "mandatory":true, "info":"Username for MongoDB admin access via Mongo Express.", "placeholder":"admin_user", "default":null }, { "id":"me_config_mongodb_adminpassword", "label":"Mongo Express Admin Password", "type":"password", "mandatory":true, "info":"Password for MongoDB admin access via Mongo Express.", "placeholder":"Enter admin password", "default":null }, { "id":"me_config_mongodb_url", "label":"MongoDB URL", "type":"text", "mandatory":true, "info":"URL for MongoDB instance accessible by Mongo Express.", "placeholder":"mongodb://localhost:27017", "default":null }, { "id":"me_config_basicauth", "label":"Basic Authentication", "type":"boolean", "mandatory":true, "info":"Enable or disable basic authentication for Mongo Express.", "placeholder":null, "default":null } ] } ] }
The definition of the field name and goal can be found in the documentation.
The value of the id
field should be:
- required;
- unique for all schema;
- in the snake_case style as to the Naming policy.
Create a file with the value of the variables
Let's create addonmart-tool/config_values.yaml
file with options
options: # Frontend api_url: http://localhost:5500 # Backend backend_mongo_username: mongo_user backend_mongo_password: password backend_mongo_host: mongo backend_mongo_port: 27017 backend_mongo_database: portal # MongoDB mongo_initdb_root_username: mongo_root mongo_initdb_root_password: example mongo_initdb_database: portal mongo_username: mongo_user mongo_password: password # Mongo Express me_config_mongodb_adminusername: mongo_root me_config_mongodb_adminpassword: example me_config_mongodb_url: mongodb://mongo_root:example@mongo:27017/ me_config_basicauth: false
Pay attention to following the Naming policy
Using generated environment variable file
Docker Compose uses config.yaml
as its configuration file. This approach does not support passing environment variable files directly in the launch command but requires defining them within the configuration file.
During the run
command processing, it always generates a final file with a set of all environment variables. The location of this file relative to the build
folder is always static and we can count on it. This file will be used in the compose file.
Using templates
In addition, a separate configuration file is required for MongoDB initialization and setup that also should be dynamically configured.
To ensure proper configuration, we’ll use template files with placeholders for custom variables. During the template processing, these placeholders will be replaced with their actual values and saved in a new file. Then these generated files will be referenced within the Docker Compose file.
We should use a separate template folder and the file name should end with .template
postfix.
Note:
- Instead of the default delimiters {{ }} around variables use {@ @}
- Variables should be in camelCase style.
Learn more in the documentation
Create a MongoDB initiation file
Make the changes for mongo/init-mongo.js.example
file and save it to addonmart-tool/templates/init-mongo.js.template
Before | After |
---|---|
mongo/init-mongo.js.example db = db.getSiblingDB('admin'); db.auth('mongo_root', 'example'); db = db.getSiblingDB('portal'); db.createUser({ user: 'mongo_user', pwd: 'password', roles: [ { role: 'readWrite', db: 'portal', }, ], }); ... other code without changes ... | addonmart-tool/templates/init-mongo.js.template db = db.getSiblingDB('admin'); db.auth('{@ meConfigMongodbAdminusername @}', '{@ mongoInitdbRootPassword @}'); db = db.getSiblingDB('{@ mongoInitdbDatabase @}'); db.createUser({ user: '{@ mongoUsername @}', pwd: '{@ mongoPassword @}', roles: [ { role: 'readWrite', db: '{@ mongoInitdbDatabase @}', }, ], }); ... other code without changes ... |
Template processing diagram
Update compose YAML file
It needs changes:
env_file
link form '.env.example' to './addonmart-tool/build/config_values.env'mongo.volumes
link from './mongo/init-mongo.js.example:/docker-entrypoint-initdb.d/init-mongo.js' to './addonmart-tool/build/configs/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js'
Listing the changed compose.yaml file
version: '3.3' services: web: build: context: './frontend' ports: - '80:80' env_file: './addonmart-tool/build/config_values.env' depends_on: - back networks: - portal-network back: build: context: './backend' target: builder ports: - '5500:80' env_file: './addonmart-tool/build/config_values.env' depends_on: - mongo networks: - portal-network mongo: container_name: mongo image: mongo ports: - 27017:27017 env_file: './addonmart-tool/build/config_values.env' volumes: - mongo_data:/data/db - ./addonmart-tool/build/configs/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js networks: - portal-network mongo-express: image: mongo-express ports: - 8081:8081 env_file: './addonmart-tool/build/config_values.env' depends_on: - mongo networks: - portal-network networks: portal-network: driver: bridge volumes: mongo_data:
First launch
Overview of the current file structure of the application folder before running (just 3 levels):
$ tree -L 3 . ├── addonmart-tool │ ├── config_schema.json │ ├── config_values.yaml │ └── templates │ └── init-mongo.js.template ├── backend │ ├── app │ │ └── main.py │ ├── Dockerfile │ └── requirements.txt ├── compose.yaml ├── frontend │ ├── angular.json │ ├── Dockerfile │ ├── nginx.conf │ ├── package.json │ ├── package-lock.json │ ├── scripts │ │ └── init_container.sh │ ├── src │ │ ├── app │ │ ├── assets │ │ ├── environments │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ └── styles.css │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json └── mongo └── init-mongo.js.example
We can launch the application with the run
command.
$ addon-mart run \ --configuration-schema ./addonmart-tool/config_schema.json \ --local-values ./addonmart-tool/config_values.yaml \ --templates ./addonmart-tool/templates \ --build ./addonmart-tool/build \ --compose-config ./compose.yaml
Pay attention to the new --templates ./addonmart-tool/templates
and --compose-config ./compose.yaml
argument
INFO: > validating configuration values... INFO: reading ./addonmart-tool/config_schema.json file... INFO: reading ./addonmart-tool/config_values.yaml file... INFO: creating ./addonmart-tool/build/config_values.env file... SUCCESS: configuration values are valid. INFO: > starting templates processing... INFO: processing init-mongo.js.template template... SUCCESS: all templates have been processed successfuly. INFO: running the app using compose.yaml... SUCCESS: the app has been successfully started. INFO: please use "docker compose --env-file ./addonmart-tool/build/config_values.env --file ./compose.yaml logs" to see logs. INFO: please use "docker compose --env-file ./addonmart-tool/build/config_values.env --file ./compose.yaml stop" to stop the app.
Checking the status and managing the container
Check the running web application on the localhost: http://localhost/. Try to use the initial credential to log in: login 'admin' with password 'admin'.
If it is OK, you should redirected to the home page with "Hi Admin! You're logged in on the Portal!!" salutation.
API specification should be available on http://localhost:5500/docs , http://localhost:5500/redoc pages. Mongo Express should be also available on http://localhost:8081/.
Recheck the Backend API:
$ curl -s localhost:5500/health | jq { "status": "OK" }
Let's look at all running application containers:
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 09d4a59a8152 step_4_web "/docker-entrypoint.…" 11 minutes ago Up 11 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp step_4-web-1 17ed1aaf25e1 mongo-express "/sbin/tini -- /dock…" 11 minutes ago Up 11 minutes 0.0.0.0:8081->8081/tcp, :::8081->8081/tcp step_4-mongo-express-1 84f311e21f7d step_4_back "uvicorn main:app --…" 11 minutes ago Up 11 minutes 0.0.0.0:5500->80/tcp, :::5500->80/tcp step_4-back-1 6e7ddc5eaefb mongo "docker-entrypoint.s…" 11 minutes ago Up 11 minutes 0.0.0.0:27017->27017/tcp, :::27017->27017/tcp mongo
Recheck the passed environment variables:
$ docker exec step_4-web-1 env | sort API_URL=http://localhost:5500 BACKEND_MONGO_DATABASE=portal BACKEND_MONGO_HOST=mongo BACKEND_MONGO_PASSWORD=password BACKEND_MONGO_PORT=27017 BACKEND_MONGO_USERNAME=mongo_user HOME=/root HOSTNAME=09d4a59a8152 ME_CONFIG_BASICAUTH=0 ME_CONFIG_MONGODB_ADMINPASSWORD=example ME_CONFIG_MONGODB_ADMINUSERNAME=mongo_root ME_CONFIG_MONGODB_URL=mongodb://mongo_root:example@mongo:27017/ MONGO_INITDB_DATABASE=portal MONGO_INITDB_ROOT_PASSWORD=example MONGO_INITDB_ROOT_USERNAME=mongo_root MONGO_PASSWORD=password MONGO_USERNAME=mongo_user NGINX_VERSION=1.25.4 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PKG_RELEASE=1
The final version of the application code at this stage is available for download via this link.
Optimization of the schema and variables
Using an auto-generated environment variable file limits us in choosing a name on the configuration schema, due to the names of the variables that need to be generated. As a result, we see excessive duplication of option values, which will always be the same.
Let's try using Templates and significantly improve the configuration. As a result, our configuration schema will be significantly simplified, which will improve its use by the end user.
Templates offer benefits over a basic set of auto-generated environment variable pairs. They can be tailored to any format and combination, eliminating redundant values.
Improved variables file
options: # Frontend api_url: http://localhost:5500 # Backend mongo_host: mongo # MongoDB mongo_root_username: mongo_root mongo_root_password: example mongo_port: 27017 mongo_database: portal mongo_username: mongo_user mongo_password: password # Mongo Express me_config_basicauth: false
Pay attention, the variable's names are also changed
Improved schema file
{ "groups":[ { "name":"Frontend", "provider":"APP_OWNER", "settings":[ { "id":"api_url", "label":"API URL", "type":"text", "mandatory":true, "info":"URL for the API endpoint that the frontend will interact with.", "placeholder":"https://api.example.com", "default":null } ] }, { "name":"Backend", "provider":"APP_OWNER", "settings":[ { "id":"mongo_host", "label":"MongoDB Host", "type":"text", "mandatory":true, "info":"Host address for the MongoDB instance used by the backend.", "placeholder":"localhost", "default":null } ] }, { "name":"MongoDB", "provider":"APP_OWNER", "settings":[ { "id":"mongo_root_username", "label":"Root Username", "type":"text", "mandatory":true, "info":"Root username for initializing MongoDB.", "placeholder":"mongo_root", "default":null }, { "id":"mongo_root_password", "label":"Root Password", "type":"password", "mandatory":true, "info":"Password for MongoDB root user during initialization.", "placeholder":"Enter root password", "default":null }, { "id":"mongo_port", "label":"MongoDB Port", "type":"integer", "mandatory":true, "info":"Port number for MongoDB connection in the backend.", "placeholder":27017, "default":null }, { "id":"mongo_database", "label":"MongoDB Database", "type":"text", "mandatory":true, "info":"MongoDB application Database.", "placeholder":"portal", "default":null }, { "id":"mongo_username", "label":"MongoDB Username", "type":"text", "mandatory":true, "info":"Username for MongoDB user access.", "placeholder":"mongo_user", "default":null }, { "id":"mongo_password", "label":"MongoDB Password", "type":"password", "mandatory":true, "info":"Password for MongoDB user access.", "placeholder":"Enter password", "default":null } ] }, { "name":"Mongo Express", "provider":"APP_OWNER", "settings":[ { "id":"me_config_basicauth", "label":"Basic Authentication", "type":"boolean", "mandatory":true, "info":"Enable or disable basic authentication for Mongo Express.", "placeholder":null, "default":null } ] } ] }
Create Environment variable template file
To ensure proper configuration, we’ll use template files with placeholders for custom variables. During the template processing, these placeholders will be replaced with their actual values and saved in a new file. These generated files will then be referenced within Docker Compose.
We should use a separate template folder and the file name should end with .template
postfix.
Note:
- Instead of the default delimiters {{ }} around variables use {@ @}
- Variables should be in camelCase style.
Learn more in the documentation
This environment variable file consists of lines with a name/value pair for each one. We use the UPPERCASE_WITH_UNDERSCORES naming style for environment variable names, but for value placeholders, we use the camelCase naming style.
# Frontend API_URL={@ apiUrl @} # Backend BACKEND_MONGO_USERNAME={@ mongoUsername @} BACKEND_MONGO_PASSWORD={@ mongoPassword @} BACKEND_MONGO_HOST={@ mongoHost @} BACKEND_MONGO_PORT={@ mongoPort @} BACKEND_MONGO_DATABASE={@ mongoDatabase @} # MongoDB MONGO_INITDB_ROOT_USERNAME={@ mongoRootUsername @} MONGO_INITDB_ROOT_PASSWORD={@ mongoRootPassword @} MONGO_INITDB_DATABASE={@ mongoDatabase @} # Mongo Express ME_CONFIG_MONGODB_ADMINUSERNAME={@ mongoRootUsername @} ME_CONFIG_MONGODB_ADMINPASSWORD={@ mongoRootPassword @} ME_CONFIG_MONGODB_URL=mongodb://{@ mongoRootUsername @}:{@ mongoRootPassword @}@{@ mongoHost @}:{@ mongoPort @}/ ME_CONFIG_BASICAUTH={@ meConfigBasicauth @}
Update compose YAML file
The env_file
value should point to the generated env file './addonmart-tool/build/configs/.env'
Listing the changed compose.yaml file
version: '3.3' services: web: build: context: './frontend' ports: - '80:80' env_file: './addonmart-tool/build/configs/.env' depends_on: - back networks: - portal-network back: build: context: './backend' target: builder ports: - '5500:80' env_file: './addonmart-tool/build/configs/.env' depends_on: - mongo networks: - portal-network mongo: container_name: mongo image: mongo ports: - 27017:27017 env_file: './addonmart-tool/build/configs/.env' volumes: - mongo_data:/data/db - ./addonmart-tool/build/configs/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js networks: - portal-network mongo-express: image: mongo-express ports: - 8081:8081 env_file: './addonmart-tool/build/configs/.env' depends_on: - mongo networks: - portal-network networks: portal-network: driver: bridge volumes: mongo_data:
Final launch
Overview of the current file structure of the addonmart-tool
folder before running:
$ tree -a addonmart-tool/ addonmart-tool/ ├── config_schema.json ├── config_values.yaml └── templates ├── .env.template └── init-mongo.js.template
We can launch the application with the run
command.
$ addon-mart run \ --configuration-schema ./addonmart-tool/config_schema.json \ --local-values ./addonmart-tool/config_values.yaml \ --templates ./addonmart-tool/templates \ --build ./addonmart-tool/build \ --compose-config ./compose.yaml INFO: > validating configuration values... INFO: reading ./addonmart-tool/config_schema.json file... INFO: reading ./addonmart-tool/config_values.yaml file... INFO: creating ./addonmart-tool/build/config_values.env file... SUCCESS: configuration values are valid. INFO: > starting templates processing... INFO: processing .env.template template... INFO: processing init-mongo.js.template template... SUCCESS: all templates have been processed successfuly. INFO: running the app using compose.yaml... SUCCESS: the app has been successfully started. INFO: please use "docker compose --env-file ./addonmart-tool/build/config_values.env --file ./compose.yaml logs" to see logs. INFO: please use "docker compose --env-file ./addonmart-tool/build/config_values.env --file ./compose.yaml stop" to stop the app.
Let's examine the template processing result for the environment variable file:
$ tree -a addonmart-tool/build/ addonmart-tool/build/ ├── configs │ ├── .env │ └── init-mongo.js └── config_values.env
# Frontend API_URL=http://localhost:5500 # Backend BACKEND_MONGO_USERNAME=mongo_user BACKEND_MONGO_PASSWORD=password BACKEND_MONGO_HOST=mongo BACKEND_MONGO_PORT=27017 BACKEND_MONGO_DATABASE=portal # MongoDB MONGO_INITDB_ROOT_USERNAME=mongo_root MONGO_INITDB_ROOT_PASSWORD=example MONGO_INITDB_DATABASE=portal # Mongo Express ME_CONFIG_MONGODB_ADMINUSERNAME=mongo_root ME_CONFIG_MONGODB_ADMINPASSWORD=example ME_CONFIG_MONGODB_URL=mongodb://mongo_root:example@mongo:27017/ ME_CONFIG_BASICAUTH=False
Check the running web application on the localhost: http://localhost/. Try to use the initial credential to log in: login 'admin' with the password 'admin'.
Recheck the Backend API:
$ curl -s localhost:5500/health | jq { "status": "OK" }
Application configuration approaches
This section provides an overview of configuration approaches for microservices. Our tutorial application's setup is simplified, with some functions streamlined or omitted, but it’s essential to consider how microservices can be configured.
There are two main approaches for configuring an application:
- Configuration via Environment Variables
- Configuration via Configuration Files
Configuration via Environment Variables
Our Frontend and Backend services use environment variables for configuration. During startup, these variables are passed to the container as key-value pairs or as an entire file.
Single-Component Application with Dockerfile
We already used this command previously here for running just the Frontend service:
$ addon-mart run \ --configuration-schema ./addonmart-tool/config_schema.json \ --local-values ./addonmart-tool/config_values.yaml \ --build ./addonmart-tool/build \ --app-dir ./ \ --app-name aom-web-app:1.0.1 \ --port 80:80
Under the hood, the config_values.env
file is created in the build directory and then passed to the container in its entirety.
Multi-Component Service with Docker Compose file
For multi-component services, the configuration is managed via a Docker Compose file. This approach allows you to specify the path to the configuration file with env_file
instructions in the Docker Compose file. An example can be found in this section.
Configuration via Configuration Files
We already introduced using configuration files in the initial application preparation for MongoDB. So, it’s crucial to adhere to the configuration file's format and syntax, based on the service's documentation or a custom parser for our services.
Templates and their processing are integral to this approach.
These templates can later be used for staging or production deployments.
For instance, the Backend configuration can utilize a configuration file located in the application folder instead of environment variables.
Example of a YAML Configuration File for Backend service:
app: mongo: username: mongo_root password: example host: mongo port: 27017 database: portal
let's create a template based on it and put the file in the templates folder with '.template' postfix in the filename:
app: mongo: username: {@ mongoUsername @} password: {@ mongoPassword @} host: {@ mongoHost @} port: {@ mongoPort @} database: {@ mongoDatabase @}
Single-Component Application with Dockerfile
In this case, we also should create templates_mapping.yaml
and point local and contained application paths accordingly.
mapping: - template: config.yaml.template location: /app/config.yaml
Do not forget to use --templates-mapping
arguments with the mapping file in the command
Multi-Component Service with Docker Compose file
For such cases, configuration is handled entirely through the Docker Compose file via volumes
instruction
... back: ... volumes: - ./addonmart-tool/build/configs/config.yaml:/app/config.yaml ...
Summary:
Working with the Application
The final version of the application code is available for download via this link.
How to Run the Application
Use the following command:
$ addon-mart run \ --configuration-schema ./addonmart-tool/config_schema.json \ --local-values ./addonmart-tool/config_values.yaml \ --templates ./addonmart-tool/templates \ --build ./addonmart-tool/build \ --compose-config ./compose.yaml
Checking the Application
Frontend: Access the frontend http://localhost/ using the provided credentials (login 'admin', password 'admin').
Backend: Refer to the documentation and example CURL request here (link) to test backend functionality.
API specification should be available on http://localhost:5500/docs , http://localhost:5500/redoc pages. Mongo Express should be also available on http://localhost:8081/.
Recheck the Backend API:
$ curl -s localhost:5500/health | jq { "status": "OK" }
Audit Tools
You have access to a complete set of Docker and Docker Compose CLI tools for container management. For example, to view logs
$ docker compose --file ./compose.yaml logs
How to Stop the Application
To stop the application, use stop
command
$ docker compose --file ./compose.yaml stop
How to Remove artifacts
$ docker compose --file ./compose.yaml down
It stops and removes containers, networks, and images created by up.
By default, the only things removed are:
- Containers for services defined in the Compose file.
- Networks defined in the networks section of the Compose file
- The default network, if one is used
$ docker compose --file ./compose.yaml down -v
Option -v, --volumes
is used for removing named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers. Note, the DB data will also be deleted.
Local Development for Marketplace
Applications intended for marketplace deployment should meet certain requirements:
- Containerized as independent components
- Configuration schema
- Add-On Mart deployment files for deploy
Early adoption of the configuration schema can help identify issues sooner and speed up the overall preparation process.
To support the preparation, we have developed tools to streamline this process:
- Add-On Mart Tool: For efficient management of AddonMart files as well as testing, submitting
- Configuration Schema UI Generator: For creating application configuration schemas
- Guides and Documentation: Public space contains manuals and guidelines
Staging and Marketplace Publication
Upon completing this guide, you can move on to the next steps in staging and publication. Details for this phase are available in the documentation for the next stage.