Simple monitoring for servers, applications and scripts
Set up in under 3 minutes, so you can focus on your business, not on outages.
Features
Monitor servers, APIs and cron jobs. Alerts by email, SMS or Telegram.
Monitoring
Servers
Docker agent with minute metrics
Applications
Health check every minute outside
Cron and scripts
Ping after each script run
Notifications
Mail on every status change
SMS
SMS when offline or critical
Telegram
Message to bot or group
Everything else
Provisioning with IaC
Targets via Terraform or Ansible
App on the go
Status on your phone too
Teams and roles
Several users per project
Infrastructure as Code is practice for us, not a buzzword
Create your application and server in the same stack as your deployment—with Terraform, Pulumi or Ansible.
Terraform
One application and one server as resources in the same module—repeatable in every environment.
Pulumi
Typed SDKs: application and server as code in TypeScript, Python or Go.
Ansible
Playbooks for your application and the server agent—idempotent and ideal for existing hosts.
resource "simple_monitoring_project" "production" {
name = "production"
}
resource "simple_monitoring_application" "checkout_api" {
project_id = simple_monitoring_project.production.id
name = "checkout-api"
health_url = "https://api.example.com/health"
}
resource "simple_monitoring_server" "api_prod" {
project_id = simple_monitoring_project.production.id
name = "api-prod-01"
register_key = "sm_server_xxx"
}import * as sm from "@doppelt-digital/simple-monitoring";
const production = new sm.Project("production", {
name: "production",
});
new sm.Application("checkout-api", {
projectId: production.id,
healthUrl: "https://api.example.com/health",
});
new sm.Server("api-prod-01", {
projectId: production.id,
registerKey: "sm_server_xxx",
});---
- name: Simple Monitoring targets
hosts: localhost
vars:
sm_project: production
register_key: "sm_server_xxx"
tasks:
- name: Register checkout-api application
doppelt_digital.simple_monitoring.application:
project: "{{ sm_project }}"
name: checkout-api
health_url: https://api.example.com/health
- name: Install server agent on api-prod-01
doppelt_digital.simple_monitoring.server:
project: "{{ sm_project }}"
name: api-prod-01
register_key: "{{ register_key }}"
docker_sock_mount: /var/run/docker.sockStraightforward pricing
Try server monitoring free: 1 server, 3 apps. Team from €10, Business from €29, Enterprise on request.
Free
Free
- 1 server
- 3 applications
- 3 cron jobs
Team
€10 per month
- 3 servers
- 20 applications
- 20 cron jobs
Business
€29 per month
- 10 servers
- 100 applications
- 100 cron jobs
Enterprise
Custom
- Servers scaled to you
- Limits tailored to contract
- Cron jobs per contract
Connect your application
HTTP health check for NestJS, Express, FastAPI, Symfony or Gin – one GET endpoint, Simple Monitoring checks uptime every minute.
NestJS
A small health controller is enough – Simple Monitoring checks the route every minute over HTTP.
Express
One health route in Express is enough – Simple Monitoring calls it every minute via GET.
FastAPI
One FastAPI endpoint – great for modern Python APIs with a clear JSON response.
Symfony
Keep health checks separate from business logic with a dedicated controller.
Gin
A health handler in Gin is enough – Simple Monitoring checks the route every minute via GET.
import { Controller, Get } from '@nestjs/common';
@Controller()
export class HealthController {
@Get('health')
health() {
return {
status: 'ok',
timestamp: new Date().toISOString(),
};
}
}import express from 'express';
const app = express();
app.get('/health', (_req, res) => {
res.status(200).json({
status: 'ok',
timestamp: new Date().toISOString(),
});
});from datetime import datetime, timezone
from fastapi import FastAPI
app = FastAPI()
@app.get('/health')
def health():
return {
'status': 'ok',
'timestamp': datetime.now(timezone.utc).isoformat(),
}<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
final class HealthController extends AbstractController
{
#[Route('/health', name: 'health', methods: ['GET'])]
public function __invoke(): JsonResponse
{
return $this->json([
'status' => 'ok',
'timestamp' => (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM),
]);
}
}package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
})
r.Run()
}Set up the server agent
Server monitoring with a Docker agent: register key from the portal, Docker socket mount, health checks and metrics.
Docker (CLI)
One container via docker run – register key, backend URL and a Docker socket mount.
Docker Compose
Repeatable setups – register key, backend URL and the Docker socket as a volume.
Kubernetes
Deployment in your cluster with a Docker socket mount – registration via register key.
docker run -d \
--name simple-monitoring-agent \
-v /var/run/docker.sock:/var/run/docker.sock \
-e REGISTER_KEY="sm_server_xxx" \
-e BACKEND_URL="https://api.example.com" \
registry.example/simple-monitoring-agent:placeholderservices:
simple-monitoring-agent:
image: registry.example/simple-monitoring-agent:placeholder
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
REGISTER_KEY: "sm_server_xxx"
BACKEND_URL: "https://api.example.com"
restart: unless-stoppedapiVersion: apps/v1
kind: Deployment
metadata:
name: simple-monitoring-agent
spec:
replicas: 1
selector:
matchLabels:
app: simple-monitoring-agent
template:
metadata:
labels:
app: simple-monitoring-agent
spec:
containers:
- name: agent
image: registry.example/simple-monitoring-agent:placeholder
env:
- name: REGISTER_KEY
value: "sm_server_xxx"
- name: BACKEND_URL
value: "https://api.example.com"
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: SocketCron jobs & scripts
Cron job monitoring via ping URL: scripts and crontab entries report each successful run – no ping means an alert.
Crontab
Add curl to your cron entry at the end – hit the ping URL from the web portal, no agent required.
GET /api/heartbeat/<token> · checked every minute
Bash script
Backups, reports or deploy scripts call the same ping URL at the end – even without crontab.
GET /api/heartbeat/<token> · checked every minute
systemd
Timer plus oneshot service: run your task, then send the ping via ExecStartPost.
GET /api/heartbeat/<token> · checked every minute
# Ping URL from the web portal (e.g. every 5 min)
*/5 * * * * curl -fsS "https://api.example.com/api/heartbeat/<token-from-portal>"#!/usr/bin/env bash
set -euo pipefail
# … run backup or report
tar -czf /var/backups/app.tgz /data/app
# ping after a successful run
curl -fsS "https://api.example.com/api/heartbeat/<token-from-portal>"# /etc/systemd/system/nightly-backup.timer
[Unit]
Description=Run backup and ping Simple Monitoring
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/nightly-backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/nightly-backup.sh
ExecStartPost=/usr/bin/curl -fsS https://api.example.com/api/heartbeat/<token-from-portal>Monitor everything now
Set up in under 3 minutes, so you can focus on your business, not on outages.