How to catch your application errors easily with Sentry and Docker!

What is Sentry?

Sentry provides open source error tracking that shows you every crash in your stack as it happens, with the details needed to prioritize, identify, reproduce, and fix each issue. It also gives you information your support team can use to reach out to and help those affected and tools that let users send you feedback for peace of mind.
Chloe Condon, Developer Evangelist(a) at @getsentry

What Sentry need for running

Sentry itself use 3 services: a web server, asynchronous workers, and a cron process.

The asynchronous worker is used for not writing directly into the database by the web server, it's an essential part if you want to scale or handle a large number of errors.

And finally, Sentry use a PostgreSQL database and a broker.

The broker is used between the web server and asynchronous workers. Sentry support both Redis and RabbitMQ, we will use Redis.

Creating our docker stack

Let's create a first stack without any Sentry configuration for now.

version: "3.4"
networks:
  internal:
    driver: overlay
    attachable: true

services:
  web:
    image: sentry:8.22
    deploy:
      restart_policy:
        condition: on-failure
    networks:
      - internal
    ports:
      - 9000:9000
    depends_on:
      - redis
      - postgres
  cron:
    image: sentry:8.22
    command: run cron
    deploy:
      restart_policy:
        condition: on-failure
    networks:
      - internal
    depends_on:
      - redis
      - postgres
  worker:
    image: sentry:8.22
    command: run worker
    deploy:
      restart_policy:
        condition: on-failure
    networks:
      - internal
    depends_on:
      - redis
      - postgres
    
  redis:
    image: redis:alpine
    networks:
      - internal

  postgres:
    image: postgres
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: always
    environment:
      POSTGRES_USER: sentry
      POSTGRES_PASSWORD: change_me

volume:
  pgdata:

Note: If you don't have a volume storage driver other than local on your Docker Swarm cluster, it could be recommended to use a Postgres service hosted by your cloud provider like Azure Database for PostgreSQL or Amazon RDS for PostgreSQL.

Adding Sentry configuration

For configuring Sentry, we will need:

  • An SMTP server
  • A secret, you can generate it through docker run --rm sentry config generate-secret-key

And we will add it using environment variables on all Sentry services:

SENTRY_EMAIL_HOST: "my.smtp.com"
SENTRY_EMAIL_PORT: 587
SENTRY_EMAIL_USER: my_user
SENTRY_EMAIL_PASSWORD: "my_password"
SENTRY_EMAIL_USE_TLS: "true"

SENTRY_SECRET_KEY: "my_secret"
SENTRY_SERVER_EMAIL: "sentry@my_domain.com"

SENTRY_POSTGRES_HOST: "postgres"
SENTRY_DB_USER: "sentry"
SENTRY_DB_PASSWORD: change_me
SENTRY_REDIS_HOST: redis

Adding deployment rules

The Web server and the asynchronous jobs worker are the two part that scales nicely, and for HA, you should have, at least, 2 of them running.

But, for the cron service, it should be only one instance at a time.

Final stack file

# sentry.yml
version: "3.4"
networks:
  internal:
    driver: overlay
    attachable: true

services:
  web:
    image: sentry:8.22
    environment:
      SENTRY_EMAIL_HOST: "my.smtp.com"
      SENTRY_EMAIL_PORT: 587
      SENTRY_EMAIL_USER: my_user
      SENTRY_EMAIL_PASSWORD: "my_password"
      SENTRY_EMAIL_USE_TLS: "true"
      SENTRY_SECRET_KEY: "my_secret"
      SENTRY_SERVER_EMAIL: "sentry@my_domain.com"
      SENTRY_POSTGRES_HOST: "postgres"
      SENTRY_DB_USER: "sentry"
      SENTRY_DB_PASSWORD: change_me
      SENTRY_REDIS_HOST: redis
    depends_on:
      - init
      - redis
      - postgres
    deploy:
      mode: replicated
      replicas: 2
      restart_policy:
        condition: on-failure
    networks:
      - internal
    ports:
      - 9000:9000

  cron:
    image: sentry:8.22
    command: run cron
    environment:
      SENTRY_EMAIL_HOST: "my.smtp.com"
      SENTRY_EMAIL_PORT: 587
      SENTRY_EMAIL_USER: my_user
      SENTRY_EMAIL_PASSWORD: "my_password"
      SENTRY_EMAIL_USE_TLS: "true"
      SENTRY_SECRET_KEY: "my_secret"
      SENTRY_SERVER_EMAIL: "sentry@my_domain.com"
      SENTRY_POSTGRES_HOST: "postgres"
      SENTRY_DB_USER: "sentry"
      SENTRY_DB_PASSWORD: change_me
      SENTRY_REDIS_HOST: redis
    depends_on:
      - init
      - redis
      - postgres
    deploy:
      mode: replicated
      replicas: 1
      restart_policy:
        condition: on-failure
    networks:
      - internal
  worker:
    image: sentry:8.22
    command: run worker
    environment:
      SENTRY_EMAIL_HOST: "my.smtp.com"
      SENTRY_EMAIL_PORT: 587
      SENTRY_EMAIL_USER: my_user
      SENTRY_EMAIL_PASSWORD: "my_password"
      SENTRY_EMAIL_USE_TLS: "true"
      SENTRY_SECRET_KEY: "my_secret"
      SENTRY_SERVER_EMAIL: "sentry@my_domain.com"
      SENTRY_POSTGRES_HOST: "postgres"
      SENTRY_DB_USER: "sentry"
      SENTRY_DB_PASSWORD: change_me
      SENTRY_REDIS_HOST: redis
    depends_on:
      - init
      - redis
      - postgres
    deploy:
      mode: replicated
      replicas: 2
      restart_policy:
        condition: on-failure
    networks:
      - internal

  redis:
    image: redis:alpine
    networks:
      - internal

  postgres:
    image: postgres
    volumes:
      - pgdata:/var/lib/postgresql/data
    deploy:
      mode: replicated
      replicas: 1
      restart_policy:
        condition: on-failure
    environment:
      POSTGRES_USER: sentry
      POSTGRES_PASSWORD: change_me
    networks:
      - internal

volumes:
  pgdata:

Deploying our stack for the first time

It's a tricky part because the stack will not run as-is. We need to run the database migration and creating our first user.

For that, we will deploy our stack for having a running Redis and Postgres:

$ docker stack deploy -c sentry.yml sentry

And now migrate/create our database:

$ docker run -ti --rm \
    --network sentry_internal \
    -e SENTRY_SECRET_KEY=my_secret \
    -e SENTRY_POSTGRES_HOST=postgres \
    -e SENTRY_DB_USER=sentry \
    -e SENTRY_DB_PASSWORD=change_me \
    -e SENTRY_REDIS_HOST=redis \
    sentry:8.22 upgrade --noinput

Create our first user:

$ docker run -ti --rm \
    --network sentry_internal \
    -e SENTRY_SECRET_KEY=my_secret \
    -e SENTRY_POSTGRES_HOST=postgres \
    -e SENTRY_DB_USER=sentry \
    -e SENTRY_DB_PASSWORD=change_me \
    -e SENTRY_REDIS_HOST=redis \
    sentry:8.22 createuser

What's next?

Ok, it's fun we have a running Sentry, but we would like to use it now. Let's go in for the first time and configure our first project.

Go to your swarm URL, in my case, it's http://localhost:9000, and configure your instance for the first time.

You can now see an empty dashboard. We will add a new project.

We will create a Node.js project.

And now, we have the instruction for updating our code with the sentry library (Raven).

Once, it's done, we should have a nice "Waiting for event" dashboard.

Conclusion

As you can see, it's straightforward to host Sentry on Docker Swarm, and, it's a perfect solution for error tracking.

Now, you can add some excellent functionalities to Sentry like Google Auth, or Github/Jira integration for creating new issues automatically or by pushing a button.

If you find a typo, have a problem when trying what you see in this article, please contact me!