DEV Community

kt
kt

Posted on Edited on

Hello World: Managing Dev.to Articles with GitOps

Introduction

As software engineers, we spend a significant amount of our time in the terminal and code editors. We love version control, we love automation, and we love plain text. So, why should our blogging experience be any different?

This is my first post managed entirely through a GitOps workflow. Instead of using the web editor, I'm writing this in Markdown locally, committing it to a Git repository, and letting automation handle the rest.

Why this approach?

There are a few key reasons why I decided to switch to this workflow:

  1. Version Control: I have a complete history of my edits. I can branch, experiment with drafts, and merge when ready.
  2. Local Environment: I can use my favorite editor (VS Code, Neovim, etc.) with all my snippets and linters.
  3. Offline Editing: I don't need an internet connection to write.
  4. Backup: My articles are stored safely in a repository, not just on a third-party platform.

How it works

The setup is surprisingly simple. Here is the workflow visualization:

github actions workflow

I have a GitHub Actions workflow that listens for changes in my articles/ directory. You can find the complete source for this setup in my repository: 0-draft/dev.to.

on:
  push:
    paths:
      - 'articles/**/*.md'
Enter fullscreen mode Exit fullscreen mode

When I push a new Markdown file with published: true in the frontmatter, the action uses the Dev.to API to publish it automatically. If I make edits later, the action updates the existing post.

Conclusion

This small bit of automation reduces friction and makes writing feel more like... well, coding. If you are interested in setting this up for yourself, check out the publish-devto GitHub Action.

Happy coding (and writing)!

Top comments (3)

Collapse
 
kasir-barati profile image
Mohammad Jawad (Kasir) Barati

Today was the worst day I had with dev.to. I wrote a post, published it, and updated it a couple of times later. Then dev.to mixed my updates and the post ended up unreadable.

That's why I now kinda hate to be at the mercy of dev.to or any other 3rd part product. I TRUST git and GitHub to be way more reliable!

Collapse
 
kasir-barati profile image
Mohammad Jawad (Kasir) Barati

So I tried, but it is not working out of the box. When I cloned your repo: github.com/0-draft/dev.to

And then removed articles you had + initializing a fresh git history it broke and cannot publish the post .github/workflows/validate.yml: https://productionresultssa4.blob.core.windows.net/actions-results/25a23108-fe86-4356-aa81-2fd18f6ba661/workflow-job-run-70f03dad-7154-5b48-8783-c513993b06b6/logs/job/job-logs.txt?rsct=text%2Fplain&se=2026-08-22T07%3A20%3A31Z&sig=Yxd9sOfJkkSun%2FK46JMxTiZdjTmcuYTq1Tghz9VrIDY%3D&ske=2026-08-22T09%3A41%3A20Z&skoid=ca7593d4-ee42-46cd-af88-8b886a2f84eb&sks=b&skt=2026-08-22T05%3A41%3A20Z&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skv=2025-11-05&sp=r&spr=https&sr=b&st=2026-08-22T07%3A10%3A26Z&sv=2025-11-05

Run set -euo pipefail
  set -euo pipefail
  if [ -n "${BASE_REF:-}" ]; then
    git fetch --no-tags origin "$BASE_REF"
    base="$(git rev-parse FETCH_HEAD)"
  elif [ -n "${BEFORE:-}" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ] \
      && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then
    base="$BEFORE"
  else
    base="$(git rev-parse HEAD^ 2>/dev/null || git rev-parse HEAD)"
  fi
  # Ensure base is not empty before writing to output
  if [ -z "$base" ]; then
    base="$(git rev-parse HEAD)"
  fi
  echo "ref=${base}" >> "$GITHUB_OUTPUT"
  echo "Comparing against $base"
  shell: /usr/bin/bash -e {0}
  env:
    pythonLocation: /opt/hostedtoolcache/Python/3.14.7/x64
    PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.14.7/x64/lib/pkgconfig
    Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.14.7/x64
    Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.14.7/x64
    Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.14.7/x64
    LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.14.7/x64/lib
    BASE_REF: 
    BEFORE: 0000000000000000000000000000000000000000
Comparing against HEAD^
a0ddcd0a94d4d3b692b7c837e480a767db2b4f35
Error: Unable to process file command 'output' successfully.
Error: Invalid format 'a0ddcd0a94d4d3b692b7c837e480a767db2b4f35'
Enter fullscreen mode Exit fullscreen mode

.github/workflows/publish.yml: https://productionresultssa18.blob.core.windows.net/actions-results/5e1a461f-989c-4367-a78a-4e4528e2f102/workflow-job-run-9e6b8c6f-9dae-56be-9c3f-c471bd0eb19f/logs/job/job-logs.txt?rsct=text%2Fplain&se=2026-08-22T07%3A19%3A21Z&sig=0QpUYTKJW0MXHVAF%2B6GgHSHGEsr5GP3SeAFMeicIMr8%3D&ske=2026-08-22T09%3A41%3A08Z&skoid=ca7593d4-ee42-46cd-af88-8b886a2f84eb&sks=b&skt=2026-08-22T05%3A41%3A08Z&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skv=2025-11-05&sp=r&spr=https&sr=b&st=2026-08-22T07%3A09%3A16Z&sv=2025-11-05

Run set -euo pipefail
  set -euo pipefail
  if [ -z "$DEVTO_TOKEN" ]; then
    echo "::error::DEVTO_API_KEY secret is not set; nothing was published."
    exit 1
  fi
  mapfile -d '' -t files < "${RUNNER_TEMP}/${ARTICLE_LIST}"

  # One invocation for the whole batch. devto-cli fetches the full remote
  # article list once per run, throttles writes internally (30 updates /
  # 30s, 10 creates / 30s) and retries 429s, so per-file loops with
  # sleeps only multiply the remote fetches.
  dev push "${files[@]}" -r "${GITHUB_REPOSITORY}" -b "${GITHUB_REF_NAME}"
  shell: /usr/bin/bash -e {0}
  env:
    DEVTO_CLI_VERSION: 1.4.0
    ARTICLE_LIST: articles.nul
    pythonLocation: /opt/hostedtoolcache/Python/3.14.7/x64
    PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.14.7/x64/lib/pkgconfig
    Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.14.7/x64
    Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.14.7/x64
    Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.14.7/x64
    LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.14.7/x64/lib
    DEVTO_TOKEN: ***
Found 1 article(s)
- Retrieving articles from dev.to…
Error: Cannot find published article on dev.to: Switched to VCS Instead of the Crappy UI of DEV.to
Push failed
Error: Process completed with exit code 255.
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kasir-barati profile image
Mohammad Jawad (Kasir) Barati

Got it all sorted out. So JFYI dear others whom are reading this comment. YOU MUST make sure to not use: id, date in your markdown file: github.com/kasir-barati/dev.to/blo...

And make sure to set published to true in your markdown if you intend on publishing it right away. If you wanted to schedule a post to be published later you can choose a date in ISO 8601 format (here I removed it but I believe it shows how you should do it).