- A Linux host (Ubuntu 22.04 LTS or similar) with 2 vCPU / 4 GB RAM / 20 GB free disk as a starting point
- Docker Engine + the Compose plugin
- A domain or subdomain with an A record pointing to the host, e.g. voice.example.com
- A Dialogflow CX agent and permission to export it (dialogflow.agents.export)
- API keys for the LLM, STT (speech-to-text) and TTS (text-to-speech) providers your Lampline build supports
lampline --help output. Everything about Dialogflow CX export is standard Google Cloud behaviour.
docker group.docker compose, not docker-compose).A voice.example.com -> <your-server-ip>, already propagated.dialogflow.agents.export (for example a Dialogflow CX Agent Editor role — confirm the exact role in the IAM docs).Check the basics before installing anything:
docker --version && docker compose version
df -h /opt
dig +short voice.example.com
Example: dig must return your server IP. If it returns nothing, fix DNS first — TLS issuance will fail otherwise.
If you are deploying on a home machine instead of a VPS, the network part differs (NAT, port forwarding, dynamic IP) — see How to add AI to your home server: self-hosted services guide.

What to do: create the working directory, install Docker if it is missing, then export your Dialogflow CX agent to a ZIP file.
Why: Lampline is a containerised service, so it needs Docker and a persistent directory. Dialogflow CX is the source of your intents, flows and training phrases — the export is the only portable artefact you can move into a self-hosted runtime.
sudo apt-get update && sudo apt-get install -y docker.io docker-compose-v2 unzip
sudo usermod -aG docker "$USER" # log out and back in afterwards
sudo mkdir -p /opt/lampline && sudo chown "$USER" /opt/lampline
cd /opt/lampline && mkdir -p data import
Export the agent in the Google Cloud console: open Dialogflow CX → your project → your agent → Agent settings (gear icon) → Export agent → Export as ZIP → Download. The browser saves something like agent-export.zip.
The REST equivalent, if you prefer scripting, is the exportAgent method on an agent resource. The API version and the exact resource path change over time, so copy the URL from the Dialogflow CX REST reference rather than from memory:
gcloud auth print-access-token > /tmp/token
curl -X POST \
-H "Authorization: Bearer $(cat /tmp/token)" \
-H "Content-Type: application/json" \
"https://dialogflow.googleapis.com/v3/projects/<project-id>/locations/<location>/agents/<agent-id>:exportAgent" \
-d '{"agentUri":"gs://<bucket>/agent-export.zip"}'
Example: a successful call returns a long-running operation object {"name": "projects/.../operations/..."} — the ZIP then appears in the bucket you named.
Copy the file to the host and unpack it only for inspection:
scp agent-export.zip <user>@voice.example.com:/opt/lampline/import/
cd /opt/lampline/import && unzip -l agent-export.zip | head -20
How to check: unzip -l lists agent.json, a flows/ directory and intent/test-case entries. If the listing is empty or the archive is 0 bytes, the download failed — repeat the console export.
| Export item | What it holds | What you will do with it |
|---|---|---|
| `agent.json` | agent-level settings: default language, time zone, speech settings | copy these values into the Lampline agent config so recognition matches |
| `flows/` | pages, routes, parameters, conditions | the flow logic you must re-create or verify after import |
| intents / training phrases | example utterances per intent | they become the utterance examples for the Lampline agent |
| webhooks | fulfillment URLs pointing at your backend | replace with URLs reachable from the Lampline host |
| test cases | regression scenarios | re-run them as scripted test calls in Step 3 |
What to do: write .env and compose.yaml, start the container, then run the importer against the uploaded archive.
Why: the container is the runtime that hosts the agent; the import converts Dialogflow CX artefacts into the internal format Lampline serves over voice.
cd /opt/lampline
cat > .env <<'EOF'
LAMPLINE_PUBLIC_URL=https://voice.example.com
LAMPLINE_ADMIN_KEY=<long-random-string>
LLM_API_KEY=<provider-key>
STT_API_KEY=<provider-key>
TTS_API_KEY=<provider-key>
EOF
chmod 600 .env
services:
lampline:
image: ghcr.io/<vendor>/lampline:<version> # exact image and tag: check the documentation
restart: unless-stopped
env_file: .env
ports:
- "127.0.0.1:8080:8080" # host port : container port — both build-specific
volumes:
- ./data:/app/data # persistent agents, prompts, call logs
- ./import:/import:ro # read-only drop zone for CX exports
docker compose pull && docker compose up -d
docker compose logs -f --tail=50 lampline
Example: the logs end with a line announcing the HTTP listener, e.g. listening on 0.0.0.0:8080. docker compose ps shows Up (healthy).
Discover the real import command instead of guessing — every build names its subcommands differently (check the documentation):
docker compose exec lampline lampline --help
docker compose exec lampline lampline agents import --help
Then run the import, for example:
docker compose exec lampline \
lampline agents import --from /import/agent-export.zip --name support-cx
How to check: the import prints a summary — number of intents, flows and utterances created, plus skipped items. Confirm the agent is registered:
curl -s -H "Authorization: Bearer $LAMPLINE_ADMIN_KEY" \
http://127.0.0.1:8080/api/agents | head
You should see support-cx in the JSON. If the response is 401, the admin key in .env and the one in the request differ.
Finally, put Caddy in front so browsers get HTTPS:
sudo caddy reverse-proxy --from voice.example.com --to 127.0.0.1:8080
Example: https://voice.example.com then resolves with a valid certificate, and the Lampline UI or health endpoint responds instead of a browser warning.

What to do: check the health endpoint, run a text turn through the agent, then place one real voice call.
Why: a green container does not prove the agent answers correctly — STT, the LLM, TTS and the imported flow are four separate links in one chain, and each fails differently.
curl -s https://voice.example.com/health
curl -s -X POST https://voice.example.com/api/agents/support-cx/test \
-H "Authorization: Bearer $LAMPLINE_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"I want to change my delivery address"}'
Endpoint paths are build-specific (check the documentation); the shape of the test is not — send one utterance you know is covered by an imported training phrase.
Example: the response contains the matched intent or flow page plus the generated reply, e.g. {"matched":"change_address","reply":"Sure — what is the new address?"}. A reply of {"matched":null} means the import skipped that intent, so re-check the import summary.
Then do one live call from a browser or a softphone to your public URL and confirm you hear the TTS voice and the transcript appears in the logs:
docker compose logs -f lampline | grep -Ei "stt|tts|turn"
Success checklist:
docker compose ps shows Up (healthy)/health returns HTTP 200 over HTTPSagents list contains support-cxERROR lines in the logsOnce the service is stable, add uptime alerting — How to Set Up VPS Monitoring via a Telegram Bot with Mini App covers the bot side; point its checks at /health.
Problem. The container will not start and the port is already taken
Error response from daemon: driver failed programming external connectivity on endpoint lampline: bind: address already in usesudo ss -ltnp | grep -E ':8080|:443'
docker compose down && docker compose up -d
Problem. The export call fails with a permission error
PERMISSION_DENIED: Permission 'dialogflow.agents.export' denied on resourcedialogflow.agents.export in IAM, wait for propagation, then retry:gcloud auth login && gcloud auth print-access-token >/dev/null && echo ok
Problem. The voice widget loads but never connects
Mixed Content: The page at 'https://voice.example.com/' was loaded over HTTPS, but requested an insecure resource 'ws://...'http:///ws:// public URL while the page is served over HTTPS.LAMPLINE_PUBLIC_URL=https://voice.example.com in .env, restart, and confirm the generated client config uses wss://:docker compose up -d && docker compose logs --tail=30 lampline
You now have a self-hosted Lampline instance behind TLS with a Dialogflow CX agent imported and verified by a text turn and a live call. Next, replace the CX webhook URLs with endpoints reachable from the Lampline host and re-run your exported test cases as scripted calls.
Can I import a Dialogflow CX agent without a Google Cloud service account?
Yes, if you export the agent as a ZIP from the console with your own user account and upload that file to the host. A service account is only needed when you call the export API from a script.
What happens to Dialogflow CX test cases and webhooks after import?
Treat them as reference material: webhook URLs that point at Google-hosted fulfillment will not work outside CX and must be replaced, and test cases have to be re-run as scripted calls against Lampline.
Where do imported agents live, and will they survive a container restart?
They are written to the mounted ./data volume. As long as you keep that volume and do not run docker compose down -v, a restart or image upgrade keeps your agents, prompts and logs.
What latency should I expect from the voice agent?
This guide does not publish a benchmark, because end-to-end latency depends on your chosen STT, LLM and TTS providers and their network distance. Measure it on your own host against the documented timing or log fields in your build.