Glitchtip is a open-source exception monitoring system, it’s similar to Sentry, which collects errors reported from your applications and helps you discover errors in real time and also helps you to understand the health of your applications.

What are we doing today?

In this tutorial we will setup Glitchtip on Docker using Traefik as our Load Balancer and SSL terminations for LetsEncrypt certificates, then we will create a Python Flask application and initiate a error so that we can see how these errors are collected by Glitchtip and we will also add an Webhook Server to demonstrate logging errors to a Webhook Endpoint for further development on errors.

So that we can see our exceptions in our applications like this:

Assumptions

I will assume that you have docker and docker-compose installed. If you need more info on Traefik you can have a look at their website, but I have also written a post on setting up Traefik v2 in detail, but we will touch on that in this post.

Code Source

I will publish the code for this tutorial under my Github Repository:

Environment Details

I have 3 DNS Entries:

  • Glitchtip: gc.civo.rbkr.xyz
  • Flask App: flask-app.civo.rbkr.xyz
  • Webhook: flask-webhook.civo.rbkr.xyz

Which all points to the host running Glitchtip.

Directory Structure

First create the directory structure for our glitchtip demo and change to the directory:

mkdir glitchtip-tutorial
cd glitchtip-tutorial

Create the traefik directory for acme.json where certificate data will be stored, create the file and change permissions on the file:

mkdir traefik
touch traefik/acme.json
chmod 600 traefik/acme.json

Traefik

Open the docker-compose.yml and add the first bit which will Traefik, ensure that you replace me@example.com with your email under certificatesResolvers.letsencrypt.acme.email:

version: '3.8'

x-logging:
  &default-logging
  driver: "json-file"
  options:
    max-size: "1m"

services:
  glitchtip-traefik:
    image: traefik:2.4
    container_name: glitchtip-traefik
    restart: unless-stopped
    volumes:
      - ./traefik/acme.json:/acme.json
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - glitchtip
    ports:
      - 80:80
      - 443:443
    command:
      - '--api'
      - '--providers.docker=true'
      - '--providers.docker.exposedByDefault=false'
      - '--entrypoints.http=true'
      - '--entrypoints.http.address=:80'
      - '--entrypoints.http.http.redirections.entrypoint.to=https'
      - '--entrypoints.http.http.redirections.entrypoint.scheme=https'
      - '--entrypoints.https=true'
      - '--entrypoints.https.address=:443'
      - '--certificatesResolvers.letsencrypt.acme.email=me@example.com'
      - '--certificatesResolvers.letsencrypt.acme.storage=acme.json'
      - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http'
      - '--log=true'
      - '--log.level=INFO'
    logging:
      driver: "json-file"
      options:
        max-size: "1m"

networks:
  glitchtip:
    name: glitchtip

You can now start Traefik so long:

docker-compose up -d

Glitchtip

Now we will add the Glitchtip components, you can refer to their documentation if you want to read more into these configuration options. Add the following services below glitchtip-traefk:

  glitchtip-postgres:
    image: postgres:13
    container_name: glitchtip-postgres
    restart: unless-stopped
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    networks:
      - glitchtip
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 5s
      retries: 5
    logging: *default-logging

  glitchtip-redis:
    image: redis
    container_name: glitchtip-redis
    restart: unless-stopped
    networks:
      - glitchtip
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 30s
      retries: 50
    logging: *default-logging

  glitchtip-web:
    image: glitchtip/glitchtip
    container_name: glitchtip-web
    restart: unless-stopped
    depends_on:
      glitchtip-traefik:
        condition: service_started
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
      GLITCHTIP_DOMAIN: "https://gt.civo.rbkr.xyz"
      ENABLE_OPEN_USER_REGISTRATION: "True"
      GLITCHTIP_MAX_EVENT_LIFE_DAYS: 90
    networks:
      - glitchtip
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.glitchtip.rule=Host(`gt.civo.rbkr.xyz`)'
      - 'traefik.http.routers.glitchtip.entrypoints=https'
      - 'traefik.http.routers.glitchtip.tls=true'
      - 'traefik.http.routers.glitchtip.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.glitchtip.service=glitchtip-service'
      - 'traefik.http.services.glitchtip-service.loadbalancer.server.port=8000'
    logging: *default-logging

  glitchtip-worker:
    image: glitchtip/glitchtip
    container_name: glitchtip-worker
    restart: unless-stopped
    command: celery -A glitchtip worker -B -l INFO
    depends_on:
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
      GLITCHTIP_DOMAIN: "https://gt.civo.rbkr.xyz"
    networks:
      - glitchtip
    logging: *default-logging

  glitchtip-migrate:
    image: glitchtip/glitchtip
    container_name: glitchtip-migrate
    depends_on:
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    command: "./manage.py migrate"
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
      GLITCHTIP_DOMAIN: "https://gt.civo.rbkr.xyz"
    networks:
      - glitchtip
    logging: *default-logging

Ensure that you change the following:

  • EMAIL_URL eg. smtp://apikey:your-password@smtp.sendgrid.net:587 (sendgrid offers free mail delivery)
  • DEFAULT_FROM_EMAIL eg. no-reply@yourdomain.com
  • Your FQDN for glitchtip in 'traefik.http.routers.glitchtip.rule=Host()'

Once your configuration is updated, start glitchtip:

docker-compose pull
docker-compose up -d

Once all the services are started you should be able to access the UI and will look more or less like this:

image

Since ENABLE_OPEN_USER_REGISTRATION is set to True, we can register our user:

image

Once you register you will be logged in and on the organization page to create a organization:

image

For this tutorial, I will be using the name my-org:

image

I will go ahead and create a application in my organisation called flask-service-dev and I will be selecting Python Flask for this tutorial, and I want to associate this project to a team called engineering, at the bottom select team and create your team:

image

Once the team has been created and selected, select the framework and name your service:

image

Once you created your project you will get a getting started guide on how to configure your application:

image

On the right hand side you will get the configuration values, which we will use as environment variables on our application that we want to emit errors to glitchtip:

  • https://6ee24c1a446347b99e9574edfe990393@gt.civo.rbkr.xyz/1
  • https://gt.civo.rbkr.xyz/api/1/security/?glitchtip_key=6ee24c1a446347b99e9574edfe990393

Now that we have signed up we can disable ENABLE_OPEN_USER_REGISTRATION by setting it to False in the docker-compose.yml, then run docker-compose up -d.

Optional: Glitchtip is written in Django, so you can also use the following method to manage users and access the admin panel on /admin:

$ docker-compose run glitchtip-migrate ./manage.py createsuperuser
Creating glitchdip_glitchtip-migrate_run ... done
Email: me@example.com
Password:
Password (again):
Superuser created successfully.

Python Flask App

Now we will create the application that we want to monitor for errors on glitchtip. From the glitchtip-tutorial directory, create the application code and dockerfile for our python flask app:

mkdir flask-app
touch flask-app/requirements.txt
touch flask-app/app.py
touch flask-app/Dockerfile

First add the requirements in flask-app/requirements.txt:

flask==1.1.2
sentry-sdk[flask]
gunicorn==20.0.4

Next our dockerfile in flask-app/Dockerfile:

FROM python:3.8-slim
ADD requirements.txt /src/requirements.txt
ADD run.sh /src/run.sh
RUN chmod +x /src/run.sh
RUN pip install --upgrade -r /src/requirements.txt
ADD app.py /src/app.py
CMD ["/src/run.sh"]

Our boot script that will start flask using gunicorn in flask-app/run.sh:

#!/bin/sh
gunicorn app:app \
	--workers 2 \
	--threads 2 \
	--bind 0.0.0.0:80 \
	--capture-output \
	--access-logfile '-' \
	--error-logfile '-'

Then our application code in flask-app/app.py:

import os
import logging
from flask import Flask
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration

GLITCHTIP_DSN = os.environ['DSN']

sentry_sdk.init(
    dsn=GLITCHTIP_DSN,
    integrations=[FlaskIntegration()]
)

app = Flask(__name__)
app.config['DEBUG'] = False

@app.before_first_request
def setup_logging():
    app.logger.addHandler(logging.StreamHandler())
    app.logger.setLevel(logging.INFO)
    app.logger.info('logs enabled')

@app.route('/')
def root():
    return 'hello, world'

@app.route('/log-error')
def log_error():
    app.logger.error('logging error with glitchtip')
    return 'logged error'

@app.route('/trigger-error')
def trigger_error():
    division_by_zero = 1 / 0

if __name__ == '__main__':
    app.logger.info('starting server')
    app.run()

Then we add this section to our docker-compose.yml:

  glitchtip-flask-app:
    build:
      context: flask-app
      dockerfile: Dockerfile
    container_name: glitchtip-flask-app
    restart: unless-stopped
    depends_on:
      glitchtip-traefik:
        condition: service_started
    environment:
      DSN: "https://6ee24c1a446347b99e9574edfe990393@gt.civo.rbkr.xyz/1"
    networks:
      - glitchtip
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.app.rule=Host(`flask-app.civo.rbkr.xyz`)'
      - 'traefik.http.routers.app.entrypoints=https'
      - 'traefik.http.routers.app.tls=true'
      - 'traefik.http.routers.app.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.app.service=app-service'
      - 'traefik.http.services.app-service.loadbalancer.server.port=80'
    logging: *default-logging

So now our full docker-compose.yml should look like this more or less:

---
version: '3.8'

x-logging:
  &default-logging
  driver: "json-file"
  options:
    max-size: "1m"

services:
  glitchtip-traefik:
    image: traefik:2.4
    container_name: glitchtip-traefik
    restart: unless-stopped
    volumes:
      - ./traefik/acme.json:/acme.json
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - glitchtip
    ports:
      - 80:80
      - 443:443
    command:
      - '--api'
      - '--providers.docker=true'
      - '--providers.docker.exposedByDefault=false'
      - '--entrypoints.http=true'
      - '--entrypoints.http.address=:80'
      - '--entrypoints.http.http.redirections.entrypoint.to=https'
      - '--entrypoints.http.http.redirections.entrypoint.scheme=https'
      - '--entrypoints.https=true'
      - '--entrypoints.https.address=:443'
      - '--certificatesResolvers.letsencrypt.acme.email=me@example.com'
      - '--certificatesResolvers.letsencrypt.acme.storage=acme.json'
      - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http'
      - '--log=true'
      - '--log.level=INFO'
    logging:
      driver: "json-file"
      options:
        max-size: "1m"

  glitchtip-postgres:
    image: postgres:13
    container_name: glitchtip-postgres
    restart: unless-stopped
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    networks:
      - glitchtip
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 5s
      retries: 5
    logging: *default-logging

  glitchtip-redis:
    image: redis
    container_name: glitchtip-redis
    restart: unless-stopped
    networks:
      - glitchtip
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 30s
      retries: 50
    logging: *default-logging

  glitchtip-web:
    image: glitchtip/glitchtip
    container_name: glitchtip-web
    restart: unless-stopped
    depends_on:
      glitchtip-traefik:
        condition: service_started
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
      GLITCHTIP_DOMAIN: "https://gt.civo.rbkr.xyz"
      ENABLE_OPEN_USER_REGISTRATION: "False"
      GLITCHTIP_MAX_EVENT_LIFE_DAYS: 90
    networks:
      - glitchtip
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.glitchtip.rule=Host(`gt.civo.rbkr.xyz`)'
      - 'traefik.http.routers.glitchtip.entrypoints=https'
      - 'traefik.http.routers.glitchtip.tls=true'
      - 'traefik.http.routers.glitchtip.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.glitchtip.service=glitchtip-service'
      - 'traefik.http.services.glitchtip-service.loadbalancer.server.port=8000'
    logging: *default-logging

  glitchtip-worker:
    image: glitchtip/glitchtip
    container_name: glitchtip-worker
    restart: unless-stopped
    command: celery -A glitchtip worker -B -l INFO
    depends_on:
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
    networks:
      - glitchtip
    logging: *default-logging

  glitchtip-migrate:
    image: glitchtip/glitchtip
    container_name: glitchtip-migrate
    depends_on:
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    command: "./manage.py migrate"
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
      GLITCHTIP_DOMAIN: "https://gt.civo.rbkr.xyz"
    networks:
      - glitchtip
    logging: *default-logging

  glitchtip-flask-app:
    build:
      context: flask-app
      dockerfile: Dockerfile
    container_name: glitchtip-flask-app
    restart: unless-stopped
    depends_on:
      glitchtip-traefik:
        condition: service_started
    environment:
      DSN: "https://6ee24c1a446347b99e9574edfe990393@gt.civo.rbkr.xyz/1"
    networks:
      - glitchtip
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.app.rule=Host(`flask-app.civo.rbkr.xyz`)'
      - 'traefik.http.routers.app.entrypoints=https'
      - 'traefik.http.routers.app.tls=true'
      - 'traefik.http.routers.app.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.app.service=app-service'
      - 'traefik.http.services.app-service.loadbalancer.server.port=80'
    logging: *default-logging

networks:
  glitchtip:
    name: glitchtip

Build the flask-app container and start the service:

docker-compose up --build -d

Making a test request:

$ curl -IL http://flask-app.civo.rbkr.xyz
HTTP/1.1 308 Permanent Redirect
Location: https://flask-app.civo.rbkr.xyz/
Date: Tue, 28 Sep 2021 06:00:27 GMT
Content-Length: 18
Content-Type: text/plain; charset=utf-8

HTTP/2 200
content-type: text/html; charset=utf-8
date: Tue, 28 Sep 2021 06:00:27 GMT
server: gunicorn/20.0.4
content-length: 12

Logging Exceptions to Glitchtip

Now let’s access the endpoints that will cause an exception, let’s first make a request to /trigger-error, then head over to “Issues”, or if you have multiple projects, select your organization, then select your project’s issues:

image

From the issues view, we can see we had one issue:

image

When we select the issue, we can see more information about this exception, such as the useragent info, the request path, where in our application the exception was raised etc:

image

As you can see, Glitchtip also provide information deeper in the stack where the exception was raised from:

image

There’s a lot of information, but what I also found nice, was that it logs span_id and trace_id, the sdk version etc:

image

If we have a look at the /log-error, which will just log an error event, from the issues view, we can see we had one issue:

image

When we select the issue, we can see more information about this exception, such as the useragent info, the request path, etc:

image

Alerting

We can setup alerting for every time we receive an exception, to do so, head over to “Settings”, “Projects” and you will find “Project Alerts”:

image

When we select “Create New Alert”, you will get the alert configuration and by default the alert will be configured to the email address of the team members of the project.

image

If we “Add Alert Recipient”, you will see that we have the option to add a “Webhook URL”, so let’s go back to our docker-compose.yml and setup a Python Flask Webhook:

  glitchtip-flask-webhook:
    build:
      context: flask-webhook
      dockerfile: Dockerfile
    container_name: glitchtip-flask-webhook
    restart: unless-stopped
    depends_on:
      glitchtip-traefik:
        condition: service_started
    networks:
      - glitchtip
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.webhook.rule=Host(`flask-webhook.civo.rbkr.xyz`)'
      - 'traefik.http.routers.webhook.entrypoints=https'
      - 'traefik.http.routers.webhook.tls=true'
      - 'traefik.http.routers.webhook.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.webhook.service=webhook-service'
      - 'traefik.http.services.webhook-service.loadbalancer.server.port=80'
    logging: *default-logging
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 5s
      timeout: 3s
      retries: 60

Then we create our flask-webhook directory:

mkdir flask-webhook

Create the required files, first our application flask-webhook/app.py:

import logging
from flask import Flask, request, jsonify

app = Flask(__name__)
app.config['DEBUG'] = False

@app.before_first_request
def setup_logging():
    app.logger.addHandler(logging.StreamHandler())
    app.logger.setLevel(logging.INFO)

@app.route('/', methods=['POST'])
def webhook():
    payload = request.get_json(force=True)
    app.logger.info(payload)
    return jsonify(payload)

@app.route('/health', methods=['GET'])
def health():
    payload = {'status': 'up'}
    return jsonify(payload)

if __name__ == '__main__':
    app.logger.info('starting server')
    app.run()

Then our flask-webhook/Dockerfile:

FROM python:3.8-slim
RUN apt update && apt install curl -y
WORKDIR /src
ADD requirements.txt /src/requirements.txt
ADD run.sh /src/run.sh
RUN chmod +x /src/run.sh
RUN pip install --upgrade -r /src/requirements.txt
ADD app.py /src/app.py

CMD ["/src/run.sh"]

Then our flask-webhook/requirements.txt:

flask==1.1.2
gunicorn==20.0.4

Then when everything is in place, build and start the container:

docker-compose up --build -d

Once the application has started, from Settings, Projects, Project Alerts, when we add a new alert recipient, we select the webhook as a recipient type and add our webhook url:

image

Now we should see:

image

Select submit and then invoke the /trigger-error url:

$ curl -I https://flask-app.civo.rbkr.xyz/trigger-error
HTTP/2 500
content-type: text/html; charset=utf-8
date: Tue, 28 Sep 2021 09:36:29 GMT
server: gunicorn/20.0.4
content-length: 290

Now when we look at the logs of our webhook container:

$ docker-compose logs -f glitchtip-flask-webhook
...
glitchtip-flask-webhook    | {'alias': 'GlitchTip', 'text': 'GlitchTip Alert', 'attachments': [{'title': 'ZeroDivisionError: division by zero', 'title_link': 'https://gt.civo.rbkr.xyz/my-org/issues/2', 'text': 'trigger_error', 'image_url': None, 'color': '#e52b50'}], 'sections': [{'activityTitle': 'ZeroDivisionError: division by zero', 'activitySubtitle': '[View Issue FLASK-SERVICE-DEV-1](https://gt.civo.rbkr.xyz/my-org/issues/2)'}]}

If we prettify it, you can see we can consume this information and do our own integrations:

>>> print(json.dumps(e, indent=2))
{
  "alias": "GlitchTip",
  "text": "GlitchTip Alert",
  "attachments": [
    {
      "title": "ZeroDivisionError: division by zero",
      "title_link": "https://gt.civo.rbkr.xyz/my-org/issues/2",
      "text": "trigger_error",
      "image_url": null,
      "color": "#e52b50"
    }
  ],
  "sections": [
    {
      "activityTitle": "ZeroDivisionError: division by zero",
      "activitySubtitle": "[View Issue FLASK-SERVICE-DEV-1](https://gt.civo.rbkr.xyz/my-org/issues/2)"
    }
  ]
}

And our email notification will look like this:

image

The Complete Compose

This will be published to my github repository, but the complete docker-compose.yml:

---
version: '3.8'

x-logging:
  &default-logging
  driver: "json-file"
  options:
    max-size: "1m"

services:
  glitchtip-traefik:
    image: traefik:2.4
    container_name: glitchtip-traefik
    restart: unless-stopped
    volumes:
      - ./traefik/acme.json:/acme.json
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - glitchtip
    ports:
      - 80:80
      - 443:443
    command:
      - '--api'
      - '--providers.docker=true'
      - '--providers.docker.exposedByDefault=false'
      - '--entrypoints.http=true'
      - '--entrypoints.http.address=:80'
      - '--entrypoints.http.http.redirections.entrypoint.to=https'
      - '--entrypoints.http.http.redirections.entrypoint.scheme=https'
      - '--entrypoints.https=true'
      - '--entrypoints.https.address=:443'
      - '--certificatesResolvers.letsencrypt.acme.email=email@example.com'
      - '--certificatesResolvers.letsencrypt.acme.storage=acme.json'
      - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http'
      - '--log=true'
      - '--log.level=INFO'
    logging:
      driver: "json-file"
      options:
        max-size: "1m"

  glitchtip-postgres:
    image: postgres:13
    container_name: glitchtip-postgres
    restart: unless-stopped
    environment:
      POSTGRES_HOST_AUTH_METHOD: "trust"
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    networks:
      - glitchtip
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 5s
      retries: 5
    logging: *default-logging

  glitchtip-redis:
    image: redis
    container_name: glitchtip-redis
    restart: unless-stopped
    networks:
      - glitchtip
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 30s
      retries: 50
    logging: *default-logging

  glitchtip-web:
    image: glitchtip/glitchtip
    container_name: glitchtip-web
    restart: unless-stopped
    depends_on:
      glitchtip-traefik:
        condition: service_started
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
      GLITCHTIP_DOMAIN: "https://gt.civo.rbkr.xyz"
      ENABLE_OPEN_USER_REGISTRATION: "False"
      GLITCHTIP_MAX_EVENT_LIFE_DAYS: 90
    networks:
      - glitchtip
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.glitchtip.rule=Host(`gt.civo.rbkr.xyz`)'
      - 'traefik.http.routers.glitchtip.entrypoints=https'
      - 'traefik.http.routers.glitchtip.tls=true'
      - 'traefik.http.routers.glitchtip.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.glitchtip.service=glitchtip-service'
      - 'traefik.http.services.glitchtip-service.loadbalancer.server.port=8000'
    logging: *default-logging

  glitchtip-worker:
    image: glitchtip/glitchtip
    container_name: glitchtip-worker
    restart: unless-stopped
    command: celery -A glitchtip worker -B -l INFO
    depends_on:
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
      GLITCHTIP_DOMAIN: "https://gt.civo.rbkr.xyz"
    networks:
      - glitchtip
    logging: *default-logging

  glitchtip-migrate:
    image: glitchtip/glitchtip
    container_name: glitchtip-migrate
    depends_on:
      glitchtip-postgres:
        condition: service_healthy
      glitchtip-redis:
        condition: service_healthy
    command: "./manage.py migrate"
    environment:
      DATABASE_URL: postgres://postgres:postgres@glitchtip-postgres:5432/postgres
      REDIS_URL: redis://glitchtip-redis:6379/0
      SECRET_KEY: jsdf93892309fhufhr
      PORT: 8000
      EMAIL_URL: "${EMAIL_URL}"
      DEFAULT_FROM_EMAIL: ${EMAIL_FROM_ADDRESS}
      GLITCHTIP_DOMAIN: "https://gt.civo.rbkr.xyz"
    networks:
      - glitchtip
    logging: *default-logging

  glitchtip-flask-app:
    build:
      context: flask-app
      dockerfile: Dockerfile
    container_name: glitchtip-flask-app
    restart: unless-stopped
    depends_on:
      glitchtip-traefik:
        condition: service_started
    environment:
      DSN: "https://6ee24c1a446347b99e9574edfe990393@gt.civo.rbkr.xyz/1"
    networks:
      - glitchtip
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.app.rule=Host(`flask-app.civo.rbkr.xyz`)'
      - 'traefik.http.routers.app.entrypoints=https'
      - 'traefik.http.routers.app.tls=true'
      - 'traefik.http.routers.app.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.app.service=app-service'
      - 'traefik.http.services.app-service.loadbalancer.server.port=80'
    logging: *default-logging

  glitchtip-flask-webhook:
    build:
      context: flask-webhook
      dockerfile: Dockerfile
    container_name: glitchtip-flask-webhook
    restart: unless-stopped
    depends_on:
      glitchtip-traefik:
        condition: service_started
    networks:
      - glitchtip
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.webhook.rule=Host(`flask-webhook.civo.rbkr.xyz`)'
      - 'traefik.http.routers.webhook.entrypoints=https'
      - 'traefik.http.routers.webhook.tls=true'
      - 'traefik.http.routers.webhook.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.webhook.service=webhook-service'
      - 'traefik.http.services.webhook-service.loadbalancer.server.port=80'
    logging: *default-logging
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 5s
      timeout: 3s
      retries: 60

networks:
  glitchtip:
    name: glitchtip

I left the domain endpoints and glitchtip application keys in the code intentionally for demonstration purposes, but deleted the stack after the time of writing.

Thank You

I hope this was helpful, I was really impressed with Glitchtip. If you liked this content, please make sure to share or come say hi on my website or twitter: