{
  "$type": "site.standard.document",
  "content": {
    "$type": "org.wordpress.html",
    "html": "<p><a href=\"https://tangled.org/\">Tangled</a> is a Github competitor built on top of atproto. The company behind it is a Finnish startup, situated in Finland, with all servers and data held in the EU. They have taken VC money, but at least conceptually the fact that it's built on top of atproto means that if the strings that came with that cash pull the company in the wrong direction, your data is still yours and you could potentially move it somewhere else.</p>\n<p>Continuing down my atproto rabbit hole, I spent some time getting a CI workflow set up for <a href=\"https://tangled.org/jola.dev/annot.at\">https://tangled.org/jola.dev/annot.at</a>. I couldn’t find any examples of Elixir CI workflows for Tangled so it took some experimenting.</p>\n<p>This uses the new <code>microvm</code> engine that was only <a href=\"https://blog.tangled.org/spindle-microvm/\">recently released</a>, which enables us to run a PostgreSQL service in the workflow. By default it only listens on a unix socket, so we have to set some extra configuration there for it to match the standard Phoenix scaffold setup.</p>\n<pre class=\"lumis\"><code class=\"language-yaml\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-property\">when</span><span class=\"l-punctuation-delimiter\">:</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-property\">event</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-punctuation-bracket\">[</span><span class=\"l-string\">&quot;push&quot;</span><span class=\"l-punctuation-delimiter\">,</span> <span class=\"l-string\">&quot;pull_request&quot;</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"3\">    <span class=\"l-property\">branch</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-punctuation-bracket\">[</span><span class=\"l-string\">&quot;main&quot;</span><span class=\"l-punctuation-bracket\">]</span>\n</div><div class=\"l-line\" data-line=\"4\">\n</div><div class=\"l-line\" data-line=\"5\"><span class=\"l-property\">engine</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">microvm</span>\n</div><div class=\"l-line\" data-line=\"6\"><span class=\"l-property\">image</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">nixos</span>\n</div><div class=\"l-line\" data-line=\"7\">\n</div><div class=\"l-line\" data-line=\"8\"><span class=\"l-property\">dependencies</span><span class=\"l-punctuation-delimiter\">:</span>\n</div><div class=\"l-line\" data-line=\"9\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-string\">beam29Packages.elixir_1_20</span>\n</div><div class=\"l-line\" data-line=\"10\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-string\">beam29Packages.erlang</span>\n</div><div class=\"l-line\" data-line=\"11\">\n</div><div class=\"l-line\" data-line=\"12\"><span class=\"l-property\">services</span><span class=\"l-punctuation-delimiter\">:</span>\n</div><div class=\"l-line\" data-line=\"13\">  <span class=\"l-property\">postgresql</span><span class=\"l-punctuation-delimiter\">:</span>\n</div><div class=\"l-line\" data-line=\"14\">    <span class=\"l-property\">enable</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-boolean\">true</span>\n</div><div class=\"l-line\" data-line=\"15\">    <span class=\"l-property\">enableTCPIP</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-boolean\">true</span>\n</div><div class=\"l-line\" data-line=\"16\">    <span class=\"l-property\">authentication</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-punctuation-delimiter\">|</span><span class=\"l-string\">\n</div><div class=\"l-line\" data-line=\"17\">      host all all 127.0.0.1/32 trust\n</div><div class=\"l-line\" data-line=\"18\">      host all all ::1/128 trust</span>\n</div><div class=\"l-line\" data-line=\"19\">\n</div><div class=\"l-line\" data-line=\"20\"><span class=\"l-property\">steps</span><span class=\"l-punctuation-delimiter\">:</span>\n</div><div class=\"l-line\" data-line=\"21\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-property\">name</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">setup</span>\n</div><div class=\"l-line\" data-line=\"22\">    <span class=\"l-property\">command</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-punctuation-delimiter\">|</span><span class=\"l-string\">\n</div><div class=\"l-line\" data-line=\"23\">      mix local.hex --force\n</div><div class=\"l-line\" data-line=\"24\">      mix local.rebar --force\n</div><div class=\"l-line\" data-line=\"25\">      mix deps.get</span>\n</div><div class=\"l-line\" data-line=\"26\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-property\">name</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">compile</span>\n</div><div class=\"l-line\" data-line=\"27\">    <span class=\"l-property\">command</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">mix compile --warnings-as-errors</span>\n</div><div class=\"l-line\" data-line=\"28\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-property\">name</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">format</span>\n</div><div class=\"l-line\" data-line=\"29\">    <span class=\"l-property\">command</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">mix format --check-formatted</span>\n</div><div class=\"l-line\" data-line=\"30\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-property\">name</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">credo</span>\n</div><div class=\"l-line\" data-line=\"31\">    <span class=\"l-property\">command</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">mix credo --strict</span>\n</div><div class=\"l-line\" data-line=\"32\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-property\">name</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">test</span>\n</div><div class=\"l-line\" data-line=\"33\">    <span class=\"l-property\">command</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">mix test --warnings-as-errors</span>\n</div><div class=\"l-line\" data-line=\"34\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-property\">name</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">unused</span>\n</div><div class=\"l-line\" data-line=\"35\">    <span class=\"l-property\">command</span><span class=\"l-punctuation-delimiter\">:</span> <span class=\"l-string\">mix deps.unlock --check-unused</span>\n</div></code></pre>\n<p>The trickiest part was using specific versions of Elixir and Erlang. You can use</p>\n<pre class=\"lumis\"><code class=\"language-yaml\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-property\">dependencies</span><span class=\"l-punctuation-delimiter\">:</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-string\">elixir</span>\n</div><div class=\"l-line\" data-line=\"3\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-string\">erlang</span>\n</div></code></pre>\n<p>but that gives you an unspecified version of Elixir. It happened to be 1.18 when I tried. Instead, you'll want to find specific NixOS packages to depend on, like the full workflow does.</p>\n<pre class=\"lumis\"><code class=\"language-yaml\" translate=\"no\" tabindex=\"0\"><div class=\"l-line\" data-line=\"1\"><span class=\"l-property\">dependencies</span><span class=\"l-punctuation-delimiter\">:</span>\n</div><div class=\"l-line\" data-line=\"2\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-string\">beam29Packages.elixir_1_20</span>\n</div><div class=\"l-line\" data-line=\"3\">  <span class=\"l-punctuation-delimiter\">-</span> <span class=\"l-string\">beam29Packages.erlang</span>\n</div></code></pre>\n<p>The BEAM 29 packages I found here <a href=\"https://mynixos.com/nixpkgs/packages/beam29Packages\">https://mynixos.com/nixpkgs/packages/beam29Packages</a>. This still doesn't give me an exact version, but it was close enough for me.</p>\n<p>I don’t have much experience with NixOS so it was a bit of trial and error to get here, but once I got the config right it runs perfect. Here’s an example run <a href=\"https://tangled.org/jola.dev/annot.at/pipelines/28076/workflow/ci.yml\">https://tangled.org/jola.dev/annot.at/pipelines/28076/workflow/ci.yml</a>.</p>\n<p>Now to go figure out how to self-host <a href=\"https://docs.tangled.org/knot-self-hosting-guide\">a knot</a> and <a href=\"https://docs.tangled.org/spindles#self-hosting-guide\">a spindle</a>. Don’t have to do that, you can use the servers Tangled offer, but I figure it’s more fun this way.</p>"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiauzlrdgzli54tyn4jg4mgb5r6kprnd7qm5ceuqssu4ytkv6srk3i"
    },
    "mimeType": "image/png",
    "size": 56662
  },
  "description": "How to set up CI workflows on Tangled for Elixir, with specific Elixir and Erlang versions, and a PostgreSQL service.",
  "path": "/posts/ci-workflows-on-tangled",
  "publishedAt": "2026-07-04T00:00:00Z",
  "site": "at://did:plc:bvraa6gajy4tfr3eh2sisdkr/site.standard.publication/3mope7jyypk22",
  "tags": [
    "atproto",
    "tangled",
    "elixir",
    "phoenix",
    "oss"
  ],
  "textContent": "Tangled is a Github competitor built on top of atproto. The company behind it is a Finnish startup, situated in Finland, with all servers and data held in the EU. They have taken VC money, but at least conceptually the fact that it's built on top of atproto means that if the strings that came with that cash pull the company in the wrong direction, your data is still yours and you could potentially move it somewhere else.\nContinuing down my atproto rabbit hole, I spent some time getting a CI workflow set up for https://tangled.org/jola.dev/annot.at. I couldn’t find any examples of Elixir CI workflows for Tangled so it took some experimenting.\nThis uses the new microvm engine that was only recently released, which enables us to run a PostgreSQL service in the workflow. By default it only listens on a unix socket, so we have to set some extra configuration there for it to match the standard Phoenix scaffold setup.\nwhen:\n  - event: [\"push\", \"pull_request\"]\n    branch: [\"main\"]\n\nengine: microvm\nimage: nixos\n\ndependencies:\n  - beam29Packages.elixir_1_20\n  - beam29Packages.erlang\n\nservices:\n  postgresql:\n    enable: true\n    enableTCPIP: true\n    authentication: |\n      host all all 127.0.0.1/32 trust\n      host all all ::1/128 trust\n\nsteps:\n  - name: setup\n    command: |\n      mix local.hex --force\n      mix local.rebar --force\n      mix deps.get\n  - name: compile\n    command: mix compile --warnings-as-errors\n  - name: format\n    command: mix format --check-formatted\n  - name: credo\n    command: mix credo --strict\n  - name: test\n    command: mix test --warnings-as-errors\n  - name: unused\n    command: mix deps.unlock --check-unused\n\nThe trickiest part was using specific versions of Elixir and Erlang. You can use\ndependencies:\n  - elixir\n  - erlang\n\nbut that gives you an unspecified version of Elixir. It happened to be 1.18 when I tried. Instead, you'll want to find specific NixOS packages to depend on, like the full workflow does.\ndependencies:\n  - beam29Packages.elixir_1_20\n  - beam29Packages.erlang\n\nThe BEAM 29 packages I found here https://mynixos.com/nixpkgs/packages/beam29Packages. This still doesn't give me an exact version, but it was close enough for me.\nI don’t have much experience with NixOS so it was a bit of trial and error to get here, but once I got the config right it runs perfect. Here’s an example run https://tangled.org/jola.dev/annot.at/pipelines/28076/workflow/ci.yml.\nNow to go figure out how to self-host a knot and a spindle. Don’t have to do that, you can use the servers Tangled offer, but I figure it’s more fun this way.",
  "title": "CI workflows on Tangled for Elixir"
}