Nuclio is an open-source serverless platform focused on high-throughput, low-latency functions for data processing and machine learning inference. Each function runs as its own container with a fast event processor in front of your code, and it can be triggered by HTTP requests, schedules or streams such as Kafka. In this tutorial you will run Nuclio on a single Ubuntu 24.04 server using Docker, deploy Python functions with the nuctl CLI, and configure HTTP and cron triggers through a function.yaml file.
Prerequisites
To follow this tutorial, you need:
- A server running Ubuntu 24.04 LTS on amd64, for example a CubePath VPS, with at least 2 vCPUs, 4 GB of RAM and 20 GB of free disk for images.
- A non-root user with
sudoprivileges. - Docker Engine installed from Docker's official repository, with your user added to the
dockergroup so you can rundockerwithoutsudo.
Confirm that Docker works for your user:
docker version --format '{{.Server.Version}}'
The command prints the Docker Engine version. If it fails with permission denied, log out and back in so your new docker group membership takes effect.
In Docker mode, Nuclio builds and runs every function as a local container. This is the simplest way to use Nuclio on one server; for multi-node deployments, Nuclio also runs on Kubernetes, which is mentioned at the end.
Step 1 - Running the Nuclio dashboard
The dashboard is the Nuclio control plane in Docker mode: a web interface and API that builds function images and starts their containers through the Docker socket. Start it with a restart policy so it survives reboots, and publish its port on the loopback interface only:
docker run -d \
--name nuclio-dashboard \
--restart unless-stopped \
-p 127.0.0.1:8070:8070 \
-v /var/run/docker.sock:/var/run/docker.sock \
quay.io/nuclio/dashboard:stable-amd64
WarningThe dashboard has no login and full access to the Docker socket, which is equivalent to root access on the server. Never publish port 8070 on a public interface.
Check that the dashboard answers:
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8070
200
To open it from your workstation, create an SSH tunnel and browse to http://localhost:8070:
ssh -L 8070:127.0.0.1:8070 your_user@your_server_ip
The dashboard shows a default project where you can create functions from templates, edit code in the browser and test them. The rest of this tutorial uses the CLI, which is easier to script and to keep in version control.
Step 2 - Installing the nuctl CLI
nuctl deploys, invokes and manages functions from the command line. Download the binary from the Nuclio releases page; this tutorial uses version 1.17.9:
export NUCLIO_VERSION=1.17.9
curl -fsSLo nuctl https://github.com/nuclio/nuclio/releases/download/${NUCLIO_VERSION}/nuctl-${NUCLIO_VERSION}-linux-amd64
sudo install -m 0755 nuctl /usr/local/bin/nuctl
rm nuctl
Verify the installation:
nuctl version
Every nuctl command in this guide uses --platform local, which tells it to deploy to the local Docker daemon instead of a Kubernetes cluster.
Step 3 - Deploying your first function
A Python function in Nuclio is a handler that receives two arguments: context, with the logger and per-worker state, and event, with the request body, headers, method and trigger information. For requests with Content-Type: application/json, Nuclio decodes the body into a dictionary.
Create a project directory and the function:
mkdir -p ~/nuclio/hello && cd ~/nuclio/hello
nano hello.py
import json
def handler(context, event):
body = event.body
if isinstance(body, (bytes, bytearray)):
body = body.decode("utf-8")
if isinstance(body, str):
body = json.loads(body) if body.strip() else {}
name = body.get("name", "World") if isinstance(body, dict) else "World"
context.logger.info_with("Greeting", name=name)
return context.Response(
body=json.dumps({"message": f"Hello, {name}!"}),
content_type="application/json",
status_code=200,
)
Deploy it with the Python 3.12 runtime. The --handler value is module:function, so hello:handler points to the handler function in hello.py:
nuctl deploy hello \
--platform local \
--path ~/nuclio/hello/hello.py \
--runtime python:3.12 \
--handler hello:handler
The first deployment pulls the Python base and builder images, so it takes a few minutes. When it finishes, the log ends with Function deploy complete and the HTTP port that Docker assigned to the function. List your functions to see the port again:
nuctl get function --platform local
NAMESPACE | NAME | PROJECT | STATE | NODE PORT | REPLICAS
nuclio | hello | default | ready | 32768 | 1/1
Invoke the function by name with nuctl:
nuctl invoke hello \
--platform local \
--method POST \
--content-type application/json \
--body '{"name": "Nuclio"}'
> Response body:
{"message": "Hello, Nuclio!"}
Or call it directly with curl, replacing 32768 with the port from your output:
curl -s -X POST http://127.0.0.1:32768 \
-H "Content-Type: application/json" \
-d '{"name": "curl"}'
{"message": "Hello, curl!"}
Step 4 - Configuring a function with function.yaml
Command-line flags are fine for a quick test, but real functions need dependencies, environment variables and several triggers. Nuclio reads all of that from a function.yaml file that you keep next to the code.
The next function checks the HTTP status of a URL. It is called over HTTP with a URL in the body, and a cron trigger also runs it every five minutes against a default URL. Create the directory and the code:
mkdir -p ~/nuclio/http-checker && cd ~/nuclio/http-checker
nano main.py
import json
import os
import requests
def handler(context, event):
url = os.environ.get("DEFAULT_URL", "https://example.com")
if event.trigger.kind != "cron" and isinstance(event.body, dict):
url = event.body.get("url", url)
try:
response = requests.get(url, timeout=10)
result = {"url": url, "status": response.status_code,
"elapsed_ms": int(response.elapsed.total_seconds() * 1000)}
except requests.RequestException as exc:
result = {"url": url, "error": str(exc)}
context.logger.info_with("Checked URL", trigger=event.trigger.kind, **result)
return context.Response(
body=json.dumps(result),
content_type="application/json",
status_code=200,
)
Now create the configuration file:
nano function.yaml
apiVersion: "nuclio.io/v1"
kind: "NuclioFunction"
metadata:
name: http-checker
spec:
description: "Checks the HTTP status of a URL"
runtime: "python:3.12"
handler: "main:handler"
env:
- name: DEFAULT_URL
value: "https://example.com"
build:
commands:
- "pip install requests"
triggers:
http:
kind: "http"
numWorkers: 2
attributes:
port: 8090
every-five-minutes:
kind: "cron"
attributes:
interval: "5m"
The file does the following:
build.commandsruns while the function image is built, sorequestsis installed once, not on every call.envsets environment variables in the function container.- The
httptrigger uses two workers and a fixed host port,8090, instead of a random one. - The
crontrigger calls the handler every five minutes. It accepts eitherintervalor a cron-styleschedulesuch as*/5 * * * *.
Deploy the function, pointing nuctl at the directory and the configuration file:
nuctl deploy http-checker \
--platform local \
--path ~/nuclio/http-checker \
--file ~/nuclio/http-checker/function.yaml
Test the HTTP trigger:
curl -s -X POST http://127.0.0.1:8090 \
-H "Content-Type: application/json" \
-d '{"url": "https://www.debian.org"}'
{"url": "https://www.debian.org", "status": 200, "elapsed_ms": 143}
WarningPorts that Docker publishes, such as
8090, are reachable on every interface of the server and bypass UFW rules. If a function must not be public, keep it behind a reverse proxy with authentication, or filter the port in theDOCKER-USERiptables chain.
Step 5 - Reading logs and managing functions
Each function runs in its own container, so its logs, including the entries written with context.logger, are available through Docker. Find the container and follow its output:
docker ps --filter "name=http-checker" --format '{{.Names}}'
docker logs --tail 20 -f "$(docker ps -q --filter name=http-checker)"
After five minutes you will see an entry with "trigger": "cron" produced by the scheduled run.
To change a function, edit the code or function.yaml and run the same nuctl deploy command again; Nuclio rebuilds the image and replaces the container. To remove a function and its container:
nuctl delete function hello --platform local
To update the dashboard to a newer release, pull the image and recreate the container with the same docker run command from Step 1:
docker pull quay.io/nuclio/dashboard:stable-amd64
docker rm -f nuclio-dashboard
Troubleshooting
The deployment fails during the build. Run the deploy again with --verbose to see the full build output. Errors in build.commands, such as a misspelled package, show up there:
nuctl deploy http-checker --platform local --path ~/nuclio/http-checker --file ~/nuclio/http-checker/function.yaml --verbose
nuctl returns permission denied on /var/run/docker.sock. Your user is not in the docker group, or you have not logged in again since adding it. Run groups to check, then log out and back in.
The function returns 503. All workers of the HTTP trigger are busy. Increase numWorkers in the trigger, or make sure the handler does not block for long periods.
The HTTP port changes after every deploy. Functions without an explicit port attribute get a random host port. Set attributes.port on the HTTP trigger, as in Step 4.
Conclusion
You now run Nuclio in Docker mode on Ubuntu 24.04, with a private dashboard, Python functions deployed through nuctl, and a function configured entirely in function.yaml with dependencies, environment variables, an HTTP trigger and a cron trigger. As next steps, add a Kafka or RabbitMQ trigger to process a stream of events, place Nginx with TLS in front of the functions you want to publish, or install Nuclio on Kubernetes with its Helm chart when you need several nodes and autoscaling.
