rCTF Docs
Overview

Instancer

Configure per-team challenge instances with Docker or Kubernetes.

The instancer integration creates one isolated challenge service per team. rCTF owns the user-facing lifecycle, and an instancer provider owns the compute backend that creates, reads, extends, and deletes the running instance.

Instanced challenges work well when teams need private mutable state, a dedicated service process, or a target that should disappear after a timeout.

Warning (Hostile workloads)

Treat challenge images as hostile. Set resource limits, keep containers or pods unprivileged, and avoid mounting host paths unless the challenge explicitly needs them.

Pick a backend#

This page covers the parts that are the same for both providers, namely the architecture, the common challenge fields, and the participant lifecycle. The provider-specific deployment guides live one level deeper:

Architecture#

The instancer path has three layers:

Select a provider

The rCTF API loads instancerProvider from rctf.d/. The provider validates challenge configs and translates rCTF lifecycle calls into Docker or Kubernetes operations underneath.

Configure a challenge

Each instanced challenge stores instancerConfig in challenge data. The common fields cover the stable challenge integration ID, timeout, exposed endpoints, and provider-specific config.

Run participant instances

Participants create, extend, inspect, and delete their instance from the challenge page. rCTF returns provider status and endpoints in the same order as the challenge’s expose array, so the frontend can line them up cleanly.

Provider configuration#

The instancerProvider config selects one provider in rctf.d/:

instancer/docker-instancer calls into the bundled Docker instancer or any compatible tiny-instancer API:

rctf.d/instancer.yaml
instancerProvider:
name: instancer/docker-instancer
options:
apiUrl: http://tiny-instancer:1337
authToken: <shared-secret>
Field or variablePurpose
instancerProvider.options.apiUrlBase URL of the Docker instancer API from the rCTF API process.
instancerProvider.options.authTokenShared token sent as rctfAuthToken in lifecycle requests.
DOCKER_INSTANCER_API_URLEnvironment override for apiUrl.
DOCKER_INSTANCER_AUTH_TOKENEnvironment override for authToken.

See Docker instancer for the deployment walkthrough and Docker-specific challenge schema.

instancer/k8s-instancer creates cluster-scoped ChallengeInstance custom resources:

rctf.d/instancer.yaml
instancerProvider:
name: instancer/k8s-instancer
options:
apiUrl: https://k8s.example.com
authToken: <service-account-token>
caCertificate: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
Field or variablePurpose
instancerProvider.options.apiUrlKubernetes API server URL.
instancerProvider.options.authTokenBearer token for a service account that can create, get, patch, and delete ChallengeInstance resources.
instancerProvider.options.caCertificateKubernetes API CA certificate. This is required by the provider.
K8S_INSTANCER_API_URLEnvironment override for apiUrl.
K8S_INSTANCER_AUTH_TOKENEnvironment override for authToken.
K8S_INSTANCER_CA_CERTIFICATEEnvironment override for caCertificate.

See Kubernetes instancer for the Terraform deployment walkthrough and Kubernetes-specific challenge schema.

Challenge configuration#

Each instanced challenge needs instancerConfig. The outer envelope (challengeIntegrationId, timeoutMilliseconds, extendable, expose) is the same regardless of which provider is active, and only the inner config body changes shape. This block is also what Konata consumes. Konata accepts both snake_case and camelCase keys and posts the camelCase form rCTF expects on the wire.

challenge.yaml
instancerConfig:
challengeIntegrationId: web-demo
timeoutMilliseconds: 600000
extendable: true
config:
# Provider-specific config.
expose:
- kind: https
hostPrefix: web-demo
containerName: app
containerPort: 8080
shouldDisplay: true
title: Challenge
FieldPurpose
challengeIntegrationIdStable ID used in Docker labels, Kubernetes resource names, and lifecycle requests. Don’t change it after launch.
timeoutMillisecondsInstance lifetime for create and extend operations.
extendableLets participants extend their instance when set to true.
configProvider-specific service or pod configuration. See Docker or Kubernetes for the schema.
exposePublic endpoints rCTF should display or keep hidden for internal routing.

The expose entries map public endpoints to a service container or pod:

FieldPurpose
kindEndpoint kind. The values are http, https, tcp, and tcp-ssl.
hostPrefixPrefix used when generating the per-instance hostname.
containerNameDocker service name or Kubernetes pod name receiving traffic.
containerPortPort inside the service container or Kubernetes service.
shouldDisplayControls whether the endpoint is shown to participants. Hidden endpoints still exist.
titleOptional display label.

Endpoint kinds#

Endpoint support is provider-specific:

KindDocker behaviorKubernetes behavior
httpTraefik HTTP router on the configured HTTP port. The default is 80.Traefik IngressRoute on the web entrypoint. The public port is 80.
httpsTraefik HTTPS router on the configured HTTPS port. The default is 443.Traefik IngressRoute on the websecure entrypoint plus an HTTP redirect route. The public port is 443.
tcpRewritten to tcp-ssl because Traefik needs SNI routing.Returned as unsupported-by-instancer with port 0. Kubernetes configs should use tcp-ssl.
tcp-sslTraefik TCP router with TLS and HostSNI on the configured TCP port. The default is 1337.Traefik IngressRouteTCP with TLS and HostSNI on port 1337.

Lifecycle and API behavior#

Participant lifecycle routes are under /api/v2/integrations/challs/:id/instance:

MethodAction
GETReturns current status, time left, and endpoints.
PUTCreates an instance.
PATCHExtends an instance when the challenge is extendable.
DELETEStops an instance.

The returned status is one of stopped, starting, running, stopping, or errored. Docker only emits stopping through rCTF’s shared response schema, while Kubernetes returns it when a custom resource has a deletion timestamp.

Captcha can protect create and extend actions:

rctf.d/captcha.yaml
captcha:
protectedEndpoints:
- instancerStart
- instancerExtend

Provider schema and the admin UI#

The instancerConfig envelope (challengeIntegrationId, timeoutMilliseconds, extendable, expose, config) is provider-agnostic. The same outer shape is accepted regardless of whether the active provider is instancer/docker-instancer or instancer/k8s-instancer. Only the inner config object is provider-specific (Docker Compose-like for docker, pods[] for Kubernetes), and the active provider validates it against its own schema.

The response comes from the provider’s own schema at request time, so it always matches what the provider will actually validate against. External tooling can fetch it to validate challenge configs before pushing them.

Dynamic admin UI#

The admin challenge editor fetches the same schema endpoint and renders the form fields directly from the returned JSON Schema. Swapping the deployment between Docker and Kubernetes shows a different set of inputs without any frontend rebuild, since every field, type, and validation rule comes from the active provider. An “advanced YAML” toggle exposes the raw config block for cases the schema-driven UI doesn’t cover.

Note (Challenge repository tooling)

Konata is a CTF challenge management tool with rCTF support, including challenge syncing, Docker image publishing, and Kubernetes manifest deployment. The archived DiceCTF Quals 2026 challenges repository is a useful reference for Konata-style challenge configuration.

Esc

Start typing to search the docs.