Update: as of 11/2022 Heroku suspended their free tier offer
morling.dev describes render to come close to the Heroku offer, with Postgres databases expiring 90 days after creation
This post describes a couple of tools, technologies and services which may be used for developing and operating a full-stack web-application, without the need to show a credit-card anywhere, as of 02/2022. Vendor lock-in’s are avoided as much as possible by aiming to build on established, vendor-independent standards.
Sample app
“Collective Color Picking” is a small app illustrating the used technologies. It allows users to pick their favorite color and share it with all other users connected to the same instance.
The app consists of a minimal web-frontend, a backend which holds the state (the picked colors of all connected users), and a database which stores the default color.
GitHub repository: https://github.com/fladdimir/collective-color-picking
Sample-App on Heroku: https://collective-color-picking.herokuapp.com
(first request may take some seconds, due to a possibly sleeping Heroku dyno)
Application structure
The app has a standard 3 tier structure.
Client-side: Vue.js with Typescript, using the radial-color-picker and a canvas to display the picked colors.
Websocket connection for sending and receiving color updates.
Server-side: Quarkus, a growingly popular Java framework built on JEE standards like CDI, JPA, Bean Validation. Websocket support is available. Seamlessly usable with mapstruct, lombok, AssertJ. PostgresQL, widespread and compatible database, locally runnable e.g. via docker.
For local development, the vite frontend dev-server can proxy incoming requests to a locally running backend.
The actual deployment uses nginx to serve the static client files and proxies /api
requests to the backend.
Tests & CI
A useful feature is the option to run Quarkus API tests not only against the application running on Java but also against a native-image, which helps to discover problems like missing @RegisterForReflection
annotations on DTOs.
Static code analysis tools like sonarqube further help to ensure code quality. Sonarqube analysis can be incorporated into the build via a gradle-plugin, and also supports visualizing the test coverage, which can be obtained by integrating the Quarkus-jacoco-extension.
The test and static analysis can be run e.g. on every push to or PR at a remote repository. git-services like GitHub (actions) and GitLab (pipelines) offer free build time on their runners for public projects.
Analysis and coverage results can then be submitted to sonarcloud, a sonarqube service which is also freely available for public projects (and offers shiny badges, too).
Hosting & Deployment
Different to major cloud platforms, Heroku offers not only free compute instances (dynos, 512 MB RAM), but also free standard PostgresQL databases (limited rows, size, connections).
To be able to switch platforms without extensive configuration changes, the application is provided in form of a docker image, which is pushed to the Heroku registry. Database credentials are provided to a started container via an environment variable (which needs further pre-processing).
The application docker image builds on top of the official nginx image and includes the frontend files and the backend in a single image, which can conveniently be created using a multi-stage docker build, so that no build dependencies are included into the final image.
Since free RAM is limited - and to reduce wake-up time after Heroku dynos were put to sleep - compiling the application backend to a native-image can be beneficial. The native-image compilation requires a large amount of memory (~6 GB for the simple app), but luckily the free build runners of GitHub are able to cope with this.
The port on which the application is supposed to listen is also provided by Heroku via environment variable. To configure nginx, the config is generated at runtime from a template with help of envsubst before the start.
On update of a dedicated branch, a corresponding GitHub action automatically builds and deploys the image, also including a health-check request after application update.
Monitoring: Logs & Metrics
For observing the state of a running application, Heroku offers dedicated add-ons for collecting and analyzing logs and a standard metrics collection, which are only available after registering a credit-card.
To circumvent this issue, the app uses a dedicated logging and metrics collection setup. All logs & metrics are sent to a free-tier grafana-cloud instance.
Application logs are sent by nginx and the Quarkus backend via syslog to a local grafana-agent, which forwards them to the grafana-cloud loki log database. Since the grafana-agent receives syslog logs only via TCP, and nginx sends syslog logs via UDP, rsyslog is additionally used for protocol translation.
Apart from log-forwarding, the local grafana-agent is also a slimmed-down prometheus in agent mode, which scrapes metrics information from a specified endpoint exposed by the Quarkus metrics extension (/q/metrics). The scraped metrics are then forwarded to the Prometheus instance of grafana-cloud.
Besides forwarding logs and metrics, the grafana-agent also acts as an OTLP trace receiver which can forward traces created by the Quarkus OTLP extension. Grafana-cloud free-tier includes a Tempo tracing backend.
The grafana-cloud connection credentials are provided via environment variables.
The complete setup is shown below:
The observability backend including grafana components can be setup locally, e.g. using docker-compose, so that the complete setup can be tested locally (which is especially important due to the amount of involved configuration).