Writing a Service Plugin
A service plugin is what makes a decky look like an SSH box, an SMB share, an MSSQL server, or whatever else. Plugins are auto-discovered from decnet/services/. You add a file, you get a service.
For runtime INI-driven custom services (no Python code at all), see Custom-Services — this page is for first-class plugins baked into the codebase.
The contract
Every plugin subclasses BaseService from decnet/services/base.py:
class BaseService(ABC):
name: str # unique slug, e.g. "ssh"
ports: list[int] # in-container listen ports
default_image: str # Docker image tag, or "build"
fleet_singleton: bool = False # True = one instance fleet-wide
@abstractmethod
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict: ...
def dockerfile_context(self) -> Path | None:
return None
Rules the composer enforces so you do not have to:
- Networking keys (
networks,ipv4_address,mac_address) are injected bydecnet/composer.py. Do not set them incompose_fragment. - If you return
"build": {"context": ...}, make suredockerfile_context()returns the same path sodecnet deploycan pre-build the image. log_targetis"ip:port"when log forwarding is on, elseNone. Pass it into the container as an env var and let the in-container rsyslog bridge handle the rest.
Registration
There is no registration step. The registry in decnet/services/registry.py walks the decnet/services/ package at import time, imports every module, and picks up every BaseService subclass via __subclasses__(). Your plugin appears in decnet services and in all_services() the moment its file exists in the right directory.
To verify:
decnet services | grep <your-slug>
Templates
If your service needs a custom image (almost all do), drop the build context under templates/<slug>/:
templates/myservice/
Dockerfile
entrypoint.sh
config/
...
Conventions the existing plugins follow:
- Base the image on
debian:bookworm-slimunless you have a reason to diverge. Heterogeneity is good — some services use Alpine, some use CentOS-derived images. - Bake an rsyslog or equivalent bridge into the image so the container emits RFC 5424 on stdout.
- Never write DECNET, honeypot, or decoy strings into the image, banners, MOTDs, config files, or user-agents. See the stealth rule in Developer-Guide.
A minimal plugin
The smallest real plugin is about 50 lines. This one wraps a pre-built image and needs no Dockerfile:
# decnet/services/echoecho.py
from decnet.services.base import BaseService
class EchoEchoService(BaseService):
"""
Tiny TCP echo service. Useful as a template and for testing the composer.
service_cfg keys:
greeting First line sent on connect. Default: empty.
"""
name = "echoecho"
ports = [7]
default_image = "ghcr.io/example/echoecho:1.0"
fleet_singleton = False
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
cfg = service_cfg or {}
env: dict = {
"NODE_NAME": decky_name,
"ECHO_GREETING": cfg.get("greeting", ""),
}
if log_target:
env["SYSLOG_TARGET"] = log_target
fragment: dict = {
"image": self.default_image,
"container_name": f"{decky_name}-echoecho",
"restart": "unless-stopped",
"environment": env,
}
return fragment
That is the whole plugin. Drop it in decnet/services/echoecho.py, run decnet services, and it shows up.
Adding a build context
If you need a custom image, reference templates/<slug>/ and implement dockerfile_context:
from pathlib import Path
from decnet.services.base import BaseService
TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" / "echoecho"
class EchoEchoService(BaseService):
name = "echoecho"
ports = [7]
default_image = "build"
def compose_fragment(self, decky_name, log_target=None, service_cfg=None):
return {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-echoecho",
"restart": "unless-stopped",
"environment": {"NODE_NAME": decky_name},
}
def dockerfile_context(self) -> Path:
return TEMPLATES_DIR
Look at decnet/services/ssh.py for a fully worked, stealth-aware example including a per-decky quarantine bind-mount.
Per-service persona config
service_cfg is the dict pulled from the matching [service.<slug>] section of the INI (see INI-Config-Format). Keep the keys documented in the class docstring — that docstring is the only user-facing reference.
Pytest coverage
Every plugin ships with tests. Drop them under tests/service_testing/test_<slug>.py. Cover at minimum:
- Instantiation + registry lookup:
all_services()["echoecho"]resolves. compose_fragmentreturns the expected keys for a givendecky_nameandservice_cfg.- Absence of DECNET / honeypot strings in rendered env, command, and template files — this is the stealth rule made executable.
- If
dockerfile_context()is set, that the path exists and contains aDockerfile.
Run pytest tests/service_testing -q before committing. Features without tests do not land — see Developer-Guide.
Checklist
- New file under
decnet/services/<slug>.py, subclassesBaseService. name,ports,default_imageset.fleet_singletonif applicable.compose_fragmentreturns networking-free compose dict.- If
default_image == "build",dockerfile_context()returns the context path. templates/<slug>/exists with a Dockerfile (if building).- No DECNET / honeypot / decoy strings anywhere the attacker can see.
service_cfgkeys documented in the class docstring.- Pytest coverage under
tests/service_testing/. decnet serviceslists the new slug.- Commit follows the style in Developer-Guide.
Related pages
- Developer-Guide — conventions, DI rules, commit style.
- Custom-Services — declarative INI-only services.
- INI-Config-Format — the deploy spec format.
- Design-Overview — where plugins fit in the larger picture.
DECNET
User docs
- Quick-Start
- Installation
- Requirements-and-Python-Versions
- CLI-Reference
- INI-Config-Format
- Custom-Services
- Services-Catalog
- Service-Personas
- Archetypes
- Distro-Profiles
- OS-Fingerprint-Spoofing
- Networking-MACVLAN-IPVLAN
- Deployment-Modes
- SWARM-Mode
- MazeNET
- Remote-Updates
- Environment-Variables
- Teardown-and-State
- Database-Drivers
- Systemd-Setup
- Logging-and-Syslog
- Service-Bus
- Web-Dashboard
- REST-API-Reference
- Mutation-and-Randomization
- Troubleshooting
Developer docs
DECNET — honeypot deception-network framework. Pre-1.0, active development — use with caution. See Sponsors to support the project. Contact: samuel@securejump.cl