AdGuard Home
Deploy AdGuard Home on Kubernetes — a network-wide DNS-level ad and tracker blocker. Supports DNS over HTTPS, DNS over TLS, custom rewrite rules, client-level settings, and optional multi-instance synchronization.
- Wizard mode (
config.adGuardHome: {}): AdGuard Home starts on port 3000 for the initial setup wizard. After completing setup, the UI moves to port 80. - Pre-configured mode (
config.adGuardHomepopulated): Setup wizard is skipped entirely. The UI starts directly on port 80, ready for production use.
Choose pre-configured mode for GitOps or reproducible deployments. Wizard mode is suitable for first-time manual setup.
Key Features
- Two deployment modes — interactive wizard or full pre-configured via values
- Dual PVC — separate
conf(config + TLS certs) andwork(query log + stats + filters) volumes - Dedicated DNS service — separate LoadBalancer for UDP/TCP port 53
- adguardhome-sync — multi-instance config synchronization (primary → replicas)
- Full-volume backup —
tararchives both PVCs to S3 (no database involved)
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install adguard-home helmforge/adguard-home -f values.yaml
OCI registry:
helm install adguard-home oci://ghcr.io/helmforgedev/helm/adguard-home -f values.yaml
Deployment Examples
# values.yaml — AdGuard Home in wizard mode (default)
# Web UI starts on port 3000 for first-time setup
# After wizard completion, UI moves to port 80
service:
dns:
type: LoadBalancer
loadBalancerIP: '192.168.1.53' # static IP for DNS (optional, MetalLB/cloud LB)
persistence:
conf:
enabled: true
size: 256Mi
work:
enabled: true
size: 5Gi # query log + filter lists + statistics
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: adguard.example.com
paths:
- path: /
pathType: Prefix# Access wizard for first-time setup:
kubectl port-forward svc/<release>-adguard-home-web 3000:80
# Visit http://localhost:3000# values.yaml — AdGuard Home pre-configured (wizard skipped, UI on port 80)
config:
adGuardHome:
http:
address: 0.0.0.0:80
session_ttl: 720h
users:
- name: admin
# Generate: htpasswd -bnBC 10 "" 'mypassword' | cut -d: -f2
password: '$2y$10$...'
dns:
bind_hosts:
- 0.0.0.0
port: 53
upstream_dns:
- 'https://dns.cloudflare.com/dns-query'
- 'https://dns.google/dns-query'
bootstrap_dns:
- 1.1.1.1
- 8.8.8.8
protection_enabled: true
filtering_enabled: true
cache_size: 4194304
filters:
- enabled: true
url: 'https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt'
name: AdGuard DNS filter
id: 1
- enabled: true
url: 'https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt'
name: AdAway Default Blocklist
id: 2
querylog:
enabled: true
interval: 720h
statistics:
enabled: true
interval: 168h
schema_version: 29
service:
dns:
type: LoadBalancer
loadBalancerIP: '192.168.1.53'
persistence:
conf:
enabled: true
size: 256Mi
work:
enabled: true
size: 5Gi
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: adguard.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: adguard-home-tls
hosts:
- adguard.example.com# values.yaml — Primary instance with adguardhome-sync to replica
# Deploy a second instance as the replica (separate helm release)
config:
adGuardHome:
http:
address: 0.0.0.0:80
users:
- name: admin
password: '$2y$10$...'
schema_version: 29
sync:
enabled: true
origin:
url: 'http://adguard-home.adguard.svc.cluster.local:80'
username: admin
password: 'admin-password' # use existingSecret in production
replicas:
- url: 'http://adguard-home-replica.adguard.svc.cluster.local:80'
username: admin
password: 'admin-password'
cron: '*/10 * * * *' # sync every 10 minutes; empty = continuous daemon
runOnStart: true
features:
dns:
serverConfig: true
accessLists: true
rewrites: true
general:
settings: true
protection: true
clients: true
filters: true
service:
dns:
type: LoadBalancer
loadBalancerIP: '192.168.1.53'# values.yaml — AdGuard Home with daily S3 backup
# Backup archives both conf and work PVCs (tar — no database)
config:
adGuardHome:
http:
address: 0.0.0.0:80
users:
- name: admin
password: '$2y$10$...'
schema_version: 29
service:
dns:
type: LoadBalancer
loadBalancerIP: '192.168.1.53'
persistence:
conf:
enabled: true
size: 256Mi
work:
enabled: true
size: 5Gi
backup:
enabled: true
schedule: '0 2 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: adguard-backups
prefix: adguard-home
existingSecret: adguard-s3-credentials # keys: access-key, secret-key
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: adguard.example.com
paths:
- path: /
pathType: PrefixConfiguration Reference
Image
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository |
string | docker.io/adguard/adguardhome |
AdGuard Home image. |
image.tag |
string | "v0.107.77" |
Image tag. |
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
config.adGuardHome |
object | {} |
Pre-seed config rendered as AdGuardHome.yaml. Empty = wizard mode. |
config.existingSecret |
string | "" |
Existing secret with AdGuardHome.yaml content (key: AdGuardHome.yaml). |
AdGuard Home requires bcrypt-hashed passwords in the configuration file. Generate with:
htpasswd -bnBC 10 "" 'mypassword' | cut -d: -f2Use the output (starting with $2y$10$...) as the password value.
Persistence — Dual PVC
| Parameter | Type | Default | Description |
|---|---|---|---|
persistence.conf.enabled |
boolean | true |
Enable PVC for /opt/adguardhome/conf (config + TLS certs). |
persistence.conf.size |
string | 256Mi |
Conf PVC size. |
persistence.conf.storageClass |
string | "" |
StorageClass for conf PVC. |
persistence.conf.existingClaim |
string | "" |
Use an existing PVC for conf. |
persistence.work.enabled |
boolean | true |
Enable PVC for /opt/adguardhome/work (query log + stats + filter lists). |
persistence.work.size |
string | 2Gi |
Work PVC size. Increase for longer query log retention. |
persistence.work.storageClass |
string | "" |
StorageClass for work PVC. |
persistence.work.existingClaim |
string | "" |
Use an existing PVC for work. |
Init Permissions
| Parameter | Type | Default | Description |
|---|---|---|---|
initPermissions.enabled |
boolean | true |
Normalize /opt/adguardhome/conf and /opt/adguardhome/work before start. |
initPermissions.image.repository |
string | docker.io/library/busybox |
Init container image repository. |
initPermissions.image.tag |
string | "1.37" |
Init container image tag. |
initPermissions.securityContext.capabilities.add |
array | [CHOWN, DAC_OVERRIDE, FOWNER] |
Baseline-compatible capabilities for PVC ownership, recursive traversal, and directory mode changes. |
initPermissions.resources |
object | {} |
Resource requests and limits for the init container. |
When securityContext.runAsUser or podSecurityContext.runAsUser is configured for a non-root runtime, pre-seed AdGuardHome.yaml with
config.adGuardHome or config.existingSecret. The interactive setup wizard requires administrator privileges on first launch;
pre-seeded mode runs with non-root UID and podSecurityContext.fsGroup.
Services
| Parameter | Type | Default | Description |
|---|---|---|---|
service.web.type |
string | ClusterIP |
Web UI service type. |
service.web.port |
integer | 80 |
Web UI port. |
service.dns.type |
string | LoadBalancer |
DNS service type. Use NodePort for bare-metal without MetalLB. |
service.dns.port |
integer | 53 |
DNS service port (UDP + TCP). |
service.dns.loadBalancerIP |
string | "" |
Static IP for the DNS LoadBalancer. |
Ingress
| Parameter | Type | Default | Description |
|---|---|---|---|
ingress.enabled |
boolean | false |
Enable Ingress for the web UI. |
ingress.ingressClassName |
string | traefik |
Ingress class name. |
ingress.hosts |
array | [] |
Host and path rules. |
ingress.tls |
array | [] |
TLS configuration. |
Sync (adguardhome-sync)
| Parameter | Type | Default | Description |
|---|---|---|---|
sync.enabled |
boolean | false |
Deploy the adguardhome-sync sidecar. |
sync.origin.url |
string | "" |
URL of the origin AdGuard Home instance. |
sync.origin.username |
string | "" |
Admin username on the origin. |
sync.origin.password |
string | "" |
Admin password on the origin. Use existingSecret. |
sync.replicas |
array | [] |
List of replica instances (url, username, password). |
sync.cron |
string | */10 * * * * |
Sync schedule. Empty string = continuous daemon mode. |
sync.runOnStart |
boolean | true |
Run sync immediately on pod startup. |
sync.existingSecret |
string | "" |
Existing secret with sync credentials. |
sync.features.dns.serverConfig |
boolean | true |
Sync DNS server configuration. |
sync.features.general.filters |
boolean | true |
Sync filter lists. |
sync.features.general.clients |
boolean | true |
Sync client settings. |
Backup
Backup archives both PVCs (conf + work) as tar archives. No database is involved.
| Parameter | Type | Default | Description |
|---|---|---|---|
backup.enabled |
boolean | false |
Enable scheduled S3 backup CronJob. |
backup.schedule |
string | "0 2 * * *" |
Cron schedule. |
backup.archivePrefix |
string | adguard-home |
Prefix for backup archive filenames. |
backup.s3.endpoint |
string | "" |
S3-compatible endpoint URL. |
backup.s3.bucket |
string | "" |
Target bucket name. |
backup.s3.existingSecret |
string | "" |
Existing secret with S3 credentials. |
extraManifests |
array | [] |
Extra Kubernetes manifests. |