# Mangrullo: a watchtower replacement

**URL:** <https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753>\
**Category:** Community\
**Tags:** announces\
**Created:** [March 4, 2026, 1:50pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753 "2026-03-04T13:50:22Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![ralsina](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/ralsina/32/2139_2.png) [@ralsina](https://forum.crystal-lang.org/u/ralsina)\
**Post date:** [March 4, 2026, 1:50pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/1 "2026-03-04T13:50:22Z")

</div>

When you self-host things you often use Docker. And when you are responsible for things running with Docker how do you keep things updated?

One answer to this used to be watchtower, a daemon that updated your docker packages. BUT … it’s abandoned. So I wrote a slightly better replacement called [Mangrullo](https://mangrullo.ralsina.me)

Crystal + Kemal + Docr + Other things as usual :-)

---

<div class="post-metadata">

**Author:** ![Xen](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/xen/32/2288_2.png) [@Xen](https://forum.crystal-lang.org/u/Xen)\
**Post date:** [March 4, 2026, 7:46pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/2 "2026-03-04T19:46:54Z")

</div>

Noice, been kinda missing a watchtower replacement.

Sidenote: anyone knows of a Crystal Docker shard that allows for listening on events?

---

<div class="post-metadata">

**Author:** ![ralsina](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/ralsina/32/2139_2.png) [@ralsina](https://forum.crystal-lang.org/u/ralsina)\
**Post date:** [March 4, 2026, 7:47pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/3 "2026-03-04T19:47:48Z")

</div>

What kind of events?

---

<div class="post-metadata">

**Author:** ![Xen](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/xen/32/2288_2.png) [@Xen](https://forum.crystal-lang.org/u/Xen)\
**Post date:** [March 4, 2026, 7:52pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/4 "2026-03-04T19:52:08Z")

</div>

Container start/stop/die primarily.

---

<div class="post-metadata">

**Author:** ![ralsina](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/ralsina/32/2139_2.png) [@ralsina](https://forum.crystal-lang.org/u/ralsina)\
**Post date:** [March 4, 2026, 7:58pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/5 "2026-03-04T19:58:40Z")

</div>

You can just run `docker events --filter type=container --filter event=start` and look at the output or something. Surely there is also a REST endpoint in the socket … maybe docr can be expanded to support it

---

<div class="post-metadata">

**Author:** ![jgaskins](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jgaskins/32/2449_2.png) [@jgaskins](https://forum.crystal-lang.org/u/jgaskins)\
**Post date:** [March 5, 2026, 6:19am UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/6 "2026-03-05T06:19:26Z")

</div>

This is what I do for Docker events. Works pretty well. I could extract it to a shard, but I haven’t yet.

```crystal
require "http/client"
require "socket"
require "json"
require "uri"

socket_path = ENV.fetch("DOCKER_HOST", "/var/run/docker.sock").lchop("unix://")
log = Log.for("docker")

UNIXSocket.open socket_path do |socket|
  http = HTTP::Client.new(socket)
  filters = URI.encode_www_form(ARGV[0]? || "")

  log.notice &.emit "Listening for Docker events", socket: socket_path
  http.get("/events?#{filters}") do |response|
    unless response.status.success?
      STDERR.puts "Error: #{response.status} – #{response.body_io.gets_to_end}"
      exit 1
    end

    response.body_io.each_line do |line|
      next if line.blank?

      event = Event.from_json(line)
      log.info &.emit event.type,
        action: event.action,
        image: event.actor.attributes["image"]?.to_s

      # Do something with the event
    end
  end
end

struct Event
  include JSON::Serializable

  @[JSON::Field(key: "Type")]
  getter type : String
  @[JSON::Field(key: "Action")]
  getter action : String
  @[JSON::Field(key: "Actor")]
  getter actor : Actor
  getter scope : String
  @[JSON::Field(key: "timeNano", converter: Time::EpochNanosConverter)]
  getter time : Time

  struct Actor
    include JSON::Serializable

    @[JSON::Field(key: "ID")]
    getter id : String
    @[JSON::Field(key: "Attributes")]
    getter attributes : Hash(String, String)
  end
end

module Time::EpochNanosConverter
  extend self

  def from_json(json : JSON::PullParser)
    Time::UNIX_EPOCH + json.read_int.nanoseconds
  end
end

```

---

<div class="post-metadata">

**Author:** ![ysbaddaden](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/ysbaddaden/32/2252_2.png) [@ysbaddaden](https://forum.crystal-lang.org/u/ysbaddaden)\
**Post date:** [March 5, 2026, 1:48pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/7 "2026-03-05T13:48:27Z")

</div>

Just saying: there are Docker shards, so you can interact with a Docker server directly (though HTTP, SSH, …).

I patched an existing one a few years ago to automatically generate an API from the OpenAPI documentation for example:

> **[Comparing jeromegn:master...ysbaddaden:master · jeromegn/docker.cr](https://github.com/jeromegn/docker.cr/compare/master...ysbaddaden:docker.cr:master)**
>
> Docker API client in Crystal. Contribute to jeromegn/docker.cr development by creating an account on GitHub.

---

<div class="post-metadata">

**Author:** ![ralsina](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/ralsina/32/2139_2.png) [@ralsina](https://forum.crystal-lang.org/u/ralsina)\
**Post date:** [March 5, 2026, 2:13pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/8 "2026-03-05T14:13:41Z")

</div>

Yeah, I use marghidanu/docr in this project

---

<div class="post-metadata">

**Author:** ![Xen](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/xen/32/2288_2.png) [@Xen](https://forum.crystal-lang.org/u/Xen)\
**Post date:** [March 5, 2026, 8:14pm UTC](https://forum.crystal-lang.org/t/mangrullo-a-watchtower-replacement/8753/9 "2026-03-05T20:14:47Z")

</div>

Great suggestions, certainly something to throw together a proof of concept.

> [@ysbaddaden](#):
>
> I patched an existing one a few years ago to automatically generate an API from the OpenAPI documentation for example:

I’ve developed an aversion to client generators. They cover the whole API and you end up with humongous amount of generated code where you end up using 5%, and you’re not quite sure which 5%. So you end up regenerating it at each new version, check that nothing broke and just commit the whole shebang and pollute your git log with unnecessary changes.

I have a little dream about a generator you tell what you need, and it just generates a very simple client that provides what you’re actually using.
