Morphic on Ubuntu 24.04 on Azure User Guide
Overview
Morphic is an open source, AI powered answer engine. You ask a question in plain language; Morphic searches the web, reads what it finds, and streams back an answer with its sources, rendering results into a generative interface rather than a list of blue links. It keeps your conversations so you can come back to a line of enquiry later, and it suggests follow up questions to take the thread further.
Morphic calls an AI provider, it does not host a model. That is what makes it cheap to run: no GPU, one small VM, and you bring your own provider key so you keep full control of which model you use and what it costs.
This cloudimg image runs Morphic v1.5.0 from the official container image, pinned to an immutable digest, with PostgreSQL 16 for chat history, Redis for the search cache and a private SearXNG instance for web search, all bound to the VM's loopback interface and fronted by nginx on port 80. Because Morphic upstream has no self hosted login of its own, cloudimg locks the whole instance behind a sign in credential that every VM generates for itself on first boot, so an instance is never left open to strangers. Backed by 24/7 cloudimg support.
What is included:
- Morphic v1.5.0 (container
ghcr.io/miurla/morphic, Apache-2.0), pinned to an immutablesha256digest so the shipped version never drifts - A private SearXNG search engine on the VM, so web search works out of the box with no search API key at all
- PostgreSQL 16 for chat history and Redis for the search result cache, both bound to
127.0.0.1with passwords generated per VM - nginx on
:80enforcing HTTP Basic authentication against a per-VM credential, with response buffering disabled so answers stream token by token - An unauthenticated
/healthzendpoint for load balancer and uptime checks - No AI provider key of any kind baked into the image — you add exactly one after launch
docker.service,postgresql.service,redis-server.service,nginx.service,morphic.serviceandmorphic-firstboot.serviceas systemd units, enabled and active- 24/7 cloudimg support
How this image is secured
Morphic upstream is designed for personal, single user, local use: authentication is optional and the only implementation it ships is hosted Supabase, which the prebuilt container cannot even use. Deployed as upstream ships it, a Morphic VM on a public IP with a working provider key would let anyone who finds it spend your AI credits. This image closes that three ways:
- nginx requires a credential on every path except the static
/healthzprobe. The username and a 24 character password are generated on this VM at first boot and written to a root only file. There is no default, shared or documented credential at any point. - Port 80 stays shut until the credential exists.
nginx.servicecarries a condition on a marker file that first boot creates only after it has minted the credential and confirmed that an unauthenticated request is refused. A customer VM cannot serve a single request before it is locked down. - The application does not start until it is configured.
morphic.servicecarries a condition on the per-VM environment file, which is absent from the image, so the enabled unit is a no-op until first boot writes it.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2ms (2 vCPU / 8 GiB RAM) is the recommended size. The idle stack uses about 1.1 GiB, but Morphic's advanced search reads and parses the pages it finds, and that crawl plus the Next.js server side rendering can spike sharply while several answers stream at once — 8 GiB gives that real headroom without a swap file. A Standard_B2s (4 GiB) is workable for a single light user; go to Standard_B4ms for a busy shared instance.
Network security group inbound rules: allow 22/tcp from your management network and 80/tcp from wherever you will use Morphic. Morphic serves plain HTTP on port 80 — for anything reachable from the internet, terminate TLS in front of it (an Azure Application Gateway or Front Door, or your own certificate on the VM's nginx) and use a real hostname.
Nothing else needs opening. The application itself (3000), PostgreSQL (5432), Redis (6379) and SearXNG (8080) are all bound to the loopback interface and are unreachable from outside the VM.
You will also need an API key from one AI provider — OpenAI, Anthropic, Google Gemini, the Vercel AI Gateway, or any OpenAI compatible endpoint. You do not need a search provider key: this image runs its own search engine.
Step 1 - Deploy from the Azure Marketplace
In the Azure portal choose Create a resource, search for Morphic on Ubuntu 24.04 LTS by cloudimg, and select Create. Pick your subscription, resource group and region, set the size to Standard_B2ms, choose SSH public key authentication with username azureuser, and allow inbound 22 and 80.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group my-morphic-rg \
--name my-morphic \
--image /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Compute/galleries/<gallery>/images/morphic-ubuntu-24-04/versions/latest \
--size Standard_B2ms \
--storage-sku StandardSSD_LRS \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard \
--os-disk-delete-option Delete --nic-delete-option Delete
# then open the port Morphic serves on
az vm open-port --resource-group my-morphic-rg --name my-morphic --port 80 --priority 900
Both commands run on your own workstation, not on the VM.
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
First boot takes roughly a minute: it generates this VM's secrets, starts the containers and only then opens port 80. If the site does not answer immediately, give it a moment and retry.
Step 4 - Confirm the services are running
sudo systemctl is-active docker postgresql redis-server nginx morphic morphic-firstboot
Expected output — six active lines:
active
active
active
active
active
active
Both containers should be up:
sudo docker ps --format 'table {{.Names}}\t{{.Status}}'
NAMES STATUS
morphic Up About a minute
morphic-searxng Up About a minute

Step 5 - Confirm only port 80 is exposed
sudo ss -tlnH | awk '{print $4}' | grep -E ':(80|3000|5432|6379|8080)$' | sort -u
Everything except nginx is on loopback:
0.0.0.0:80
127.0.0.1:3000
127.0.0.1:5432
127.0.0.1:6379
127.0.0.1:8080
[::1]:6379
[::]:80
Only :80 accepts connections from the network, and that is the port nginx guards with your credential. The application, database, cache and search engine are reachable only from the VM itself.
Step 6 - Retrieve your per-VM sign in credential
Every VM generates its own username and password on first boot. They are written to a root only file:
sudo cat /root/morphic-credentials.txt
# Morphic - generated on first boot by morphic-firstboot.service
# These values are unique to this VM. Store them somewhere safe.
morphic.url=http://10.0.0.11/
morphic.web.username=cloudimg-401b30
morphic.web.password=************************
morphic.redis.password=********************************
morphic.postgres.password=********************************
The username is randomised per VM, so no two cloudimg Morphic instances share one. morphic.url records the address the VM resolved for itself at first boot; if your VM has a public IP, browse to that instead.

Step 7 - Confirm the instance refuses anonymous access
This is the check worth doing before you put a provider key on the machine. An unauthenticated request must be refused:
curl -s -o /dev/null -w 'anonymous: HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'default admin:admin: HTTP %{http_code}\n' -u admin:admin http://127.0.0.1/
curl -s -o /dev/null -w 'healthz: HTTP %{http_code}\n' http://127.0.0.1/healthz
anonymous: HTTP 401
default admin:admin: HTTP 401
healthz: HTTP 200
The chat API — the route that would spend your provider credits — is refused the same way:
curl -s -o /dev/null -w 'anonymous chat API: HTTP %{http_code}\n' \
-X POST -H 'Content-Type: application/json' -d '{"messages":[]}' \
http://127.0.0.1/api/chat
anonymous chat API: HTTP 401
Now confirm your own credential is accepted:
curl -s -o /dev/null -w 'per-VM credential: HTTP %{http_code}\n' \
-u '<MORPHIC_WEB_USERNAME>:<MORPHIC_WEB_PASSWORD>' http://127.0.0.1/
per-VM credential: HTTP 200

Step 8 - Add your AI provider key
This is the one thing you must configure. Morphic calls an external model, and this image ships no key of any kind, so until you add one Morphic will load but will not be able to answer.
Open the environment file:
sudo nano /etc/morphic/morphic.env
At the bottom you will find one commented line per supported provider:
# OPENAI_API_KEY=
# ANTHROPIC_API_KEY=
# GOOGLE_GENERATIVE_AI_API_KEY=
# AI_GATEWAY_API_KEY=
# OLLAMA_BASE_URL=http://127.0.0.1:11434
# OPENAI_COMPATIBLE_API_KEY=
# OPENAI_COMPATIBLE_API_BASE_URL=
Uncomment exactly one, paste your key after the =, save, and restart:
sudo systemctl restart morphic
Morphic detects which providers have keys and lists their models in the picker at the bottom right of the compose box. A few notes:
- OpenAI, Anthropic, Google and the Vercel AI Gateway are the straightforward choices — set the key and the models appear.
- Any OpenAI compatible endpoint (DeepSeek, Moonshot, Together, your own gateway) works by setting
OPENAI_COMPATIBLE_API_KEYandOPENAI_COMPATIBLE_API_BASE_URLtogether. If the provider has no/v1/modelsendpoint, also setOPENAI_COMPATIBLE_MODELSto a comma separated list. - Ollama, if you want a fully local model, must be installed separately and the model must support both tool calling and reasoning — Morphic requests a thinking stream, and a model without it returns
does not support thinking. Qwen3 models work; Llama 3.2 does not. A CPU only VM this size will be slow, so a hosted provider is the better choice for everyday use.
Your key is stored in a 0600 root owned file and is never written to the image, a log or the database.
Step 9 - Web search, with no search API key
Morphic's other half is search, and this image runs its own private SearXNG instance on 127.0.0.1:8080, so this works before you have added any provider key at all:
curl -s -m 90 -u '<MORPHIC_WEB_USERNAME>:<MORPHIC_WEB_PASSWORD>' \
-X POST -H 'Content-Type: application/json' \
-d '{"query":"azure virtual machine","maxResults":3,"searchDepth":"basic"}' \
http://127.0.0.1/api/advanced-search | jq -r '.results[] | .title'
Real, live results:
Microsoft Azure
Microsoft Azure portal
Azure Virtual Machines

If you would rather use a commercial search provider, set SEARCH_API=tavily (or exa, or firecrawl) in /etc/morphic/morphic.env along with that provider's key, and restart. Most people will not need to.
Step 10 - Sign in to the web interface
Browse to http://<vm-public-ip>/. The browser asks for a username and password: use morphic.web.username and morphic.web.password from Step 6.

Once past the prompt you get Morphic's home screen: an ask box, the model picker showing whichever provider you configured, a web search toggle, and shortcut chips for common intents.

Step 11 - Ask your first question
Type a question and press Enter. Morphic searches, reads the results, and streams an answer back as it is generated, showing the model's working as it goes.

When the answer completes, Morphic offers follow up questions and stores the conversation. Open the sidebar from the top left to see your history, which is held in the co-hosted PostgreSQL database on this VM and nowhere else.

Answer quality is a function of the model you chose — a frontier model gives noticeably better synthesis and citation than a small local one.
Step 12 - Confirm the datastores are private
Redis refuses unauthenticated commands:
redis-cli -h 127.0.0.1 PING
NOAUTH Authentication required.
It answers with this VM's own password:
redis-cli -h 127.0.0.1 -a '<MORPHIC_REDIS_PASSWORD>' --no-auth-warning PING
PONG
PostgreSQL holds the chat history and accepts only this VM's password:
PGPASSWORD='<MORPHIC_POSTGRES_PASSWORD>' psql -w -h 127.0.0.1 -U morphic -d morphic \
-tAc "SELECT string_agg(table_name, ', ' ORDER BY table_name) FROM information_schema.tables WHERE table_schema='public';"
chats, feedback, messages, parts
Neither port is reachable from outside the VM, and the postgres superuser has no password at all — it is local socket authentication only.
Step 13 - Confirm the shipped images are the pinned versions
sudo docker inspect --format '{{.Config.Image}}' morphic
sudo docker inspect --format '{{.Config.Image}}' morphic-searxng
ghcr.io/miurla/morphic@sha256:15c7c4be2e4ec4fe3c6fd7f47c51eb74745428a58b6c65bca4a4ce7aef3e59f3
searxng/searxng@sha256:854f239d5c181db9c4282cd0ffc1737a61232dd6ad0ea964028b96df17d7758a
Both are referenced by immutable digest rather than a moving tag, so the software on this VM cannot change underneath you. The Morphic digest is the image upstream's own release pipeline built from the commit tagged v1.5.0.
Changing the sign in credential
To set your own password for the existing user:
sudo htpasswd -B /etc/nginx/morphic.htpasswd '<MORPHIC_WEB_USERNAME>'
sudo systemctl reload nginx
To use a different username, add it and remove the old one:
sudo htpasswd -B /etc/nginx/morphic.htpasswd alice
sudo htpasswd -D /etc/nginx/morphic.htpasswd '<MORPHIC_WEB_USERNAME>'
sudo systemctl reload nginx
Remember to update your own record of the credential — /root/morphic-credentials.txt is written once at first boot and is not rewritten afterwards.
Sharing the instance
This is a single tenant appliance. Everyone who signs in shares one Morphic identity and therefore one chat history — that is how upstream behaves when its optional Supabase authentication is not configured, and the prebuilt container cannot enable it. Adding more Basic auth users controls who may reach the instance, not who sees which conversations. If you need per user histories, run one VM per person, or build Morphic from source against your own Supabase project.
Serving Morphic on your own domain
Terminate TLS in front of the VM with an Azure Application Gateway or Front Door pointed at port 80, or add your own certificate to nginx at /etc/nginx/sites-available/cloudimg-morphic and open 443. Keep the auth_basic directives in place when you edit that file — they are what stops strangers using your provider account. Morphic streams its answers, so if you put your own proxy in front, disable response buffering there too.
Maintenance
Ubuntu security updates are applied automatically by unattended-upgrades. To restart the stack after changing configuration:
sudo systemctl restart morphic
To view application logs:
sudo docker logs --tail 50 morphic
To back up your chat history:
sudo -u postgres pg_dump morphic > ~/morphic-backup.sql
Support
cloudimg provides 24/7 support for this image. Contact us at support@cloudimg.co.uk.