William Mulianto

Git Tags, Drone CI, and Watchtower — A Simple Deployment Pipeline

· 4 min read

My CI/CD setup has changed a lot over the years. I started with Jenkins like most people, struggled with it, and eventually landed on something much simpler. Here’s how my current pipeline works.

The Jenkins Days

I used Jenkins for a while. It works, but maintaining it felt like a job on its own. Plugin updates breaking things, Groovy pipelines that nobody wants to debug, and a UI that feels like it’s from 2008. For a small team or solo developer, Jenkins is overkill.

I needed something lighter.

Git Tags as the Trigger

Before talking about the CI tool itself — the most important change I made was switching to git tags as my deployment trigger instead of pushing to a branch.

The flow is simple:

  1. Work on a feature branch
  2. Merge to main when ready
  3. Tag a release when I want to deploy
git tag v1.2.0
git push origin v1.2.0

This gives me full control over what gets deployed and when. Merging to main doesn’t automatically trigger a build — only a tag does. It also makes rollbacks straightforward since every deployment maps to a version.

Drone CI

I replaced Jenkins with Drone CI. It’s lightweight, runs in a single Docker container, and uses a simple YAML config. No plugins to manage, no Groovy scripts.

Here’s a simplified .drone.yml:

kind: pipeline
type: docker
name: build

trigger:
  event:
    - tag
  ref:
    - refs/tags/v*

steps:
  - name: build-and-push
    image: plugins/docker
    settings:
      registry: registry.example.com
      repo: registry.example.com/team/myapp
      username:
        from_secret: docker_username
      password:
        from_secret: docker_password
      tags:
        - latest
        - ${DRONE_TAG}
        - ${DRONE_COMMIT_SHA:0:8}
      cache_from:
        - registry.example.com/team/myapp:latest
      build_args:
        - APP_VERSION=${DRONE_TAG}

A few things I like about this setup:

  • refs/tags/v* — only triggers on tags starting with v, so random tags don’t accidentally deploy
  • plugins/docker — handles the Docker build and push in one step, no need for docker:dind or mounting the socket
  • Three tagslatest for Watchtower, the version tag for traceability, and the short commit SHA for debugging
  • cache_from — pulls the previous latest image as cache so builds are fast
  • build_args — passes the version into the build so the app knows what version it’s running
  • Secrets — registry credentials are stored in Drone’s secret manager, never in the YAML

When I push a git tag, Drone picks it up, builds the Docker image, tags it three ways, and pushes it to the registry. That’s it.

Watchtower for Auto-Redeploy

Here’s where it gets nice. On the server, I run Watchtower — a container that watches for new Docker image versions and automatically restarts containers when a new image is available.

But I don’t want Watchtower to update every container. Some things I want to update manually. So I use the label-based approach — Watchtower only updates containers that have a specific label.

Run Watchtower with label filtering:

watchtower:
  image: containrrr/watchtower
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
  command: --label-enable --interval 60

Then on containers I want auto-updated, I add the label:

myapp:
  image: myregistry/myapp:latest
  labels:
    - "com.centurylinklabs.watchtower.enable=true"

Containers without that label are left alone. This way, databases, reverse proxies, and other infrastructure stay untouched — only the app containers get redeployed automatically.

The Full Flow

  1. I push a git tag (v1.2.0)
  2. Drone CI builds the Docker image and pushes it to the registry as both v1.2.0 and latest
  3. Watchtower detects the new latest image within 60 seconds
  4. Watchtower pulls the new image and restarts only the labeled containers
  5. Done — zero manual SSH, near zero downtime

The whole pipeline is lightweight. Drone runs in a single container, Watchtower runs in a single container. No complex orchestration, no Kubernetes, no managed CI service bills.

Key Takeaways

  • Git tags give you explicit control over what gets deployed
  • Drone CI is a great Jenkins replacement for small teams — simple YAML, no plugin hell
  • Watchtower with --label-enable lets you auto-deploy only the containers you choose
  • You don’t need a complex setup to deploy with confidence

Related