diff --git a/kubernetes/main/apps/dev/forgejo-runner/app/config.yaml b/kubernetes/main/apps/dev/forgejo-runner/app/config.yaml
new file mode 100644
index 00000000..74e620a2
--- /dev/null
+++ b/kubernetes/main/apps/dev/forgejo-runner/app/config.yaml
@@ -0,0 +1,111 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: runner-config
+data:
+  config.yml: |
+    log:
+      # The level of logging, can be trace, debug, info, warn, error, fatal
+      level: info
+      # The level of logging for jobs, can be trace, debug, info, earn, error, fatal
+      job_level: info
+
+    runner:
+      # Where to store the registration result.
+      file: .runner
+      # Execute how many tasks concurrently at the same time.
+      capacity: 3
+      # Extra environment variables to run jobs.
+      envs:
+        # A_TEST_ENV_NAME_1: a_test_env_value_1
+        # A_TEST_ENV_NAME_2: a_test_env_value_2
+      # Extra environment variables to run jobs from a file.
+      # It will be ignored if it's empty or the file doesn't exist.
+      env_file: .env
+      # The timeout for a job to be finished.
+      # Please note that the Forgejo instance also has a timeout (3h by default) for the job.
+      # So the job could be stopped by the Forgejo instance if it's timeout is shorter than this.
+      timeout: 3h
+      # The timeout for the runner to wait for running jobs to finish when
+      # shutting down because a TERM or INT signal has been received.  Any
+      # running jobs that haven't finished after this timeout will be
+      # cancelled.
+      # If unset or zero the jobs will be cancelled immediately.
+      shutdown_timeout: 3h
+      # Whether skip verifying the TLS certificate of the instance.
+      insecure: false
+      # The timeout for fetching the job from the Forgejo instance.
+      fetch_timeout: 5s
+      # The interval for fetching the job from the Forgejo instance.
+      fetch_interval: 2s
+      # The interval for reporting the job status and logs to the Forgejo instance.
+      report_interval: 1s
+      # The labels of a runner are used to determine which jobs the runner can run, and how to run them.
+      # Like: ["macos-arm64:host", "ubuntu-latest:docker://node:20-bookworm", "ubuntu-22.04:docker://node:20-bookworm"]
+      # If it's empty when registering, it will ask for inputting labels.
+      # If it's empty when executing the `daemon`, it will use labels in the `.runner` file.
+      labels: [
+        "docker:docker://node:20-bullseye",
+        // ubuntu
+        "ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest",
+        "ubuntu-24.04:docker://ghcr.io/catthehacker/ubuntu:act-24.04",
+        "ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04",
+        "ubuntu-20.04:docker://ghcr.io/catthehacker/ubuntu:act-20.04",
+        "ubuntu-18.04:docker://ghcr.io/catthehacker/ubuntu:act-18.04",
+      ]
+
+    cache:
+      # Enable cache server to use actions/cache.
+      enabled: true
+      # The directory to store the cache data.
+      # If it's empty, the cache data will be stored in $HOME/.cache/actcache.
+      dir: "/cache"
+      # The host of the cache server.
+      # It's not for the address to listen, but the address to connect from job containers.
+      # So 0.0.0.0 is a bad choice, leave it empty to detect automatically.
+      host: ""
+      # The port of the cache server.
+      # 0 means to use a random available port.
+      port: 0
+      # The external cache server URL. Valid only when enable is true.
+      # If it's specified, it will be used to set the ACTIONS_CACHE_URL environment variable. The URL should generally end with "/".
+      # Otherwise it will be set to the the URL of the internal cache server.
+      external_server: ""
+
+    container:
+      # Specifies the network to which the container will connect.
+      # Could be host, bridge or the name of a custom network.
+      # If it's empty, create a network automatically.
+      network: ""
+      # Whether to create networks with IPv6 enabled. Requires the Docker daemon to be set up accordingly.
+      # Only takes effect if "network" is set to "".
+      enable_ipv6: false
+      # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
+      privileged: false
+      # And other options to be used when the container is started (eg, --add-host=my.forgejo.url:host-gateway).
+      options:
+      # The parent directory of a job's working directory.
+      # If it's empty, /workspace will be used.
+      workdir_parent:
+      # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob
+      # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.
+      # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:
+      # valid_volumes:
+      #   - data
+      #   - /src/*.json
+      # If you want to allow any volume, please use the following configuration:
+      # valid_volumes:
+      #   - '**'
+      valid_volumes: []
+      # overrides the docker client host with the specified one.
+      # If "-" or "", an available docker host will automatically be found.
+      # If "automount", an available docker host will automatically be found and mounted in the job container (e.g. /var/run/docker.sock).
+      # Otherwise the specified docker host will be used and an error will be returned if it doesn't work.
+      docker_host: "-"
+      # Pull docker image(s) even if already present
+      force_pull: false
+
+    host:
+      # The parent directory of a job's working directory.
+      # If it's empty, $HOME/.cache/act/ will be used.
+      workdir_parent:
diff --git a/kubernetes/main/apps/dev/forgejo-runner/app/kustomization.yaml b/kubernetes/main/apps/dev/forgejo-runner/app/kustomization.yaml
index a1dab3f1..972a7beb 100644
--- a/kubernetes/main/apps/dev/forgejo-runner/app/kustomization.yaml
+++ b/kubernetes/main/apps/dev/forgejo-runner/app/kustomization.yaml
@@ -2,4 +2,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
 resources:
 - ./reg-token.sops.yaml
-- ./runner-dep.yaml
\ No newline at end of file
+- ./kustomization.yaml
+- ./runner-dep.yaml
diff --git a/kubernetes/main/apps/dev/forgejo-runner/app/runner-dep.yaml b/kubernetes/main/apps/dev/forgejo-runner/app/runner-dep.yaml
index 5c39eda6..a8b98f0a 100644
--- a/kubernetes/main/apps/dev/forgejo-runner/app/runner-dep.yaml
+++ b/kubernetes/main/apps/dev/forgejo-runner/app/runner-dep.yaml
@@ -22,6 +22,9 @@ spec:
         emptyDir: {}
       - name: runner-data
         emptyDir: {}
+      - name: runner-config
+        configMap:
+          name: runner-config
       # Initialise our configuration file using offline registration
       # https://forgejo.org/docs/v1.21/admin/actions/#offline-registration
       initContainers:
@@ -63,6 +66,9 @@ spec:
           mountPath: /certs
         - name: runner-data
           mountPath: /data
+        - name: runner-config
+          mountPath: /data/config.yml
+          subPath: config.yml
       - name: daemon
         image: docker:27.4.1-dind
         env:
@@ -72,4 +78,4 @@ spec:
           privileged: true
         volumeMounts:
         - name: docker-certs
-          mountPath: /certs
\ No newline at end of file
+          mountPath: /certs