Self-Hosted Gitea and Drone

Published: 2022-09-11

This page is about setting up Gitea with Drone as a CI server.

Getting Started

To get started, ensure that you can SSH into a server and make a new directory called gitea. We'll place three files in this directory, namely:

Let's start at the beginning and write the following to

set -e

mkdir -p /data/gitea
mkdir -p /data/gitea_config
mkdir -p /data/gitea_db
chown -R 1000:1000 /data/gitea*
chmod 775 /data/gitea*
chmod g+s /data/gitea*

mkdir -p /data/drone
chown -R 1001:1001 /data/drone
chmod 775 /data/drone
chmod g+s /data/drone


After creating the script, run it with:

$ sh

This script is idempotent, so you can run it as often as you like.

The aim is to put everything in the right place for Docker Compose. This "putting in the right place" improves security. As is visible in the script, the gitea folders get a 1000:1000 UID and GUID. The drone folder gets a 1001:1001 UID and GUID. This allows running the gitea and drone containers as non-root which, in turn, makes it harder for attackers to gain root access and read our secret files such as DRONE_GITEA_CLIENT_SECRET.env! Thanks to Docker volumes and file system permissions for the chmod tips.


To enable SSH, setup A (IPv4) and AAAA (IPv6) records in your domains DNS settings for git and drone. Point both to your server IP. Once you are sure that your DNS records for and propagated, you can add the domains to your reverse proxy. (These sentences will certainly score you points at a LAN party.)

Specifically, if you use Caddy, then add: {
} {

Caddy will automatically setup HTTPS.

Next, the following docker-compose.yml will give you a working Gitea instance:

version: '3'

    image: 'gitea/gitea:1.21.1-rootless'
    container_name: 'gitea'
      - '3000:3000'
      - '/data/gitea:/data:rw'
      - '/data/gitea_db:/var/lib/gitea:rw'
      - '/data/gitea_config:/etc/gitea:rw'
    user: '1000:1000'
      driver: "json-file"
        max-size: "10m"
        max-file: "10"
      USER_UID: '1000'
      USER_GID: '1000'
      APP_NAME: 'git'
      DEFAULT_UI_LOCATION: 'europe/amsterdam'
      DISABLE_SSH: 'true'
      DOMAIN: ''
      INSTALL_LOCK: 'true'
      LOCAL_ROOT_URL: 'http://localhost:3000/'
      OFFLINE_MODE: 'true'
      ROOT_URL: ''
    restart: 'unless-stopped'

Run it via:

$ docker compose up

and check for errors in the printed output. If that looks good, test that you can access Gitea at

Once the server is running and is accessible via the web, hit CTRL + C and start the service as a background job:

$ docker compose up -d

Now setup an admin user by stepping into the container:

$ docker exec -it gitea sh

Verify that you're inside the container by testing that Gitea is installed:

/var/lib/gitea $ gitea --version

If that works, you can create an admin user:

/var/lib/gitea $ gitea admin user create --username=<username> --password=<password> --email=<email>

Finally, don't forget to verify the installation by setting up a test repository and shutting down the server (power cycle). After about 30 seconds, your Gitea instance should be running again and show your test repository.


As a CI server, we'll use Drone here since it looked mature and easy to setup.

To set it up, go into the Gitea web interface and add a OAuth2 application to your account ( with some Application Name and as the Redirect URI.

After that, add the following Drone config including the Client ID and Client Secret that Gitea provided to the existing docker-compose.yml:

version: '3'

    image: 'drone/drone:2.12'
    container_name: 'drone'
      - '444:444'
      - '/data/drone:/data:rw'
    user: '1001:1001'
      DRONE_GITEA_CLIENT_ID: '<client id>'
      DRONE_RPC_SECRET: '<rpc secret>'
      DRONE_SERVER_PORT: ':444'
      DRONE_SERVER_PROTO: 'https'
      DRONE_LOGS_DEBUG: 'false'
      DRONE_USER_CREATE: 'username:<gitea admin username>,admin:true'
      DRONE_TLS_AUTOCERT: 'false'
    restart: 'unless-stopped'

To connect Drone to Gitea, restart the services:

$ docker compose down


$ docker compose up

Then, make sure that drone is running at a domain that you've set up (

Go to, click on login, and authorize Drone to Gitea. Once you've verified that Drone can see your Gitea repositories, restart the server and check that things still work.

Drone Runner

Finally, we can setup a Drone Runner 🏃‍️️. This should be the easiest step. No domain settings are required, just a system connected to the internet. It can even be the system that you're working on because you probably only need CI when you are working 💭. Otherwise, a small server for a few bucks per month should be fine too for most use-cases. On the Runner system of your chosing, setup something along the following lines:

version: '3'

        image: 'drone/drone-runner-docker:1'
        container_name: 'drone_runner'
            - '/var/run/docker.sock:/var/run/docker.sock'
            DRONE_RPC_PROTO: 'https'
            DRONE_SERVER_PROTO: 'http'
            DRONE_SERVER_TOKEN: '<server token; see the Drone interface>'
            DRONE_RPC_HOST: ''
            DRONE_RUNNER_CAPACITY: '2'
            DRONE_RUNNER_NAME: '<a nice name like I donno "Red Lightning">'
            - 'DRONE_RPC_SECRET.env'
        restart: 'unless-stopped'

See the Drone documentation for more information.

That's it, you should now have a working Gitea and Drone server! 🚀

The text is licensed under CC BY-NC-SA 4.0 and the code under Unlicense.