Ta
Developer Tools Azure

Tailcall on Ubuntu 24.04 on Azure User Guide

| Product: Tailcall on Ubuntu 24.04 LTS on Azure

Overview

Tailcall is an open source GraphQL platform that turns your existing REST, gRPC and GraphQL services into a single, unified GraphQL API. You describe how to compose your upstreams with a small set of declarative schema directives, and the gateway resolves each field by calling the right upstream, batching and caching requests along the way. There is no resolver code to write and no runtime to provision: Tailcall ships as a single self contained native binary.

The cloudimg image installs the official Tailcall binary at /usr/local/bin/tailcall, running as the tailcall service bound to loopback 127.0.0.1:8000, behind nginx on port 80. nginx is ready for your TLS certificate.

Works out of the box: the image ships a starter configuration at /etc/tailcall/config.graphql that composes the public jsonplaceholder.typicode.com REST API into GraphQL, so a real query returns composed data the moment the VM boots. You then replace the configuration with your own upstreams and restart the service.

Secure by default, no baked credential: Tailcall is an API gateway; its GraphQL endpoint is the product's function and is reachable by design. The shipped starter config fronts only a public demo API and defines no admin interface and no secret, so this image honestly carries no baked or shared credential. The gateway binds loopback and is exposed only through nginx, which serves just the GraphQL endpoint and a health check. Any secrets your own upstreams require are supplied by you at runtime, never baked into the image.

Note on the interface: Tailcall is an API first product. The GraphQL endpoint answers POST requests at /graphql; there is no bundled browser UI, so you drive it with curl, a GraphQL client, or your own application. This guide uses curl.

What is included:

  • Tailcall (verified at v1.6.14) as the official single native binary
  • tailcall.service bound to 127.0.0.1:8000, running as a dedicated non root user
  • nginx reverse proxy on port 80, ready for TLS
  • A working starter config at /etc/tailcall/config.graphql
  • tailcall-firstboot.service that resolves the instance endpoint URL and writes a root only info note
  • Ubuntu 24.04 LTS base, fully patched, unattended security upgrades enabled
  • 24/7 cloudimg support, 24h response SLA

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet with a subnet. Tailcall is lightweight; throughput scales with the upstreams it calls. Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) for evaluation and light workloads, or a Standard_D2s_v5 or larger for production traffic.

Step 1: Deploy from the Azure Portal

Search the Marketplace for Tailcall on Ubuntu 24.04, choose your VM size, and attach an NSG that allows TCP 22 (SSH) from your management network and TCP 80 / 443 (the GraphQL endpoint) from the networks and applications that need to query it. Front port 80 with TLS in production (see the HTTPS section below).

Step 2: Deploy from the Azure CLI

RG="tailcall-prod"; LOCATION="eastus"; VM_NAME="tailcall-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/tailcall-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name tailcall-vnet --address-prefix 10.90.0.0/16 --subnet-name tailcall-subnet --subnet-prefix 10.90.1.0/24
az network nsg create -g "$RG" --name tailcall-nsg
az network nsg rule create -g "$RG" --nsg-name tailcall-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name tailcall-nsg --name allow-web --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 443 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name tailcall-vnet --subnet tailcall-subnet --nsg tailcall-nsg --public-ip-sku Standard

Step 3: Connect via SSH

ssh azureuser@<vm-ip>

Step 4: Verify the services are running

Tailcall listens on loopback 127.0.0.1:8000 and nginx fronts it on port 80. The health endpoint is served directly by nginx and always answers when the proxy is up.

sudo systemctl start tailcall nginx 2>/dev/null || true
sudo systemctl is-active tailcall nginx
ss -tln | grep -E ':80 |127.0.0.1:8000'
echo "GET /health -> HTTP $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/health)  $(curl -s http://127.0.0.1/health)"

You should see both services active, the two listening sockets, and GET /health -> HTTP 200 ok.

Terminal showing the tailcall and nginx services active, Tailcall bound to loopback 8000 with nginx fronting port 80, and the health endpoint returning HTTP 200

Step 5: Send your first GraphQL query

The starter config exposes a Query type backed by the public jsonplaceholder REST API, plus an offline greet resolver. POST a GraphQL query to /graphql and the gateway resolves each field by calling the upstream.

curl -s -H 'Content-Type: application/json' \
  -d '{"query":"{ posts { id title } }"}' http://127.0.0.1/graphql | head -c 400; echo
curl -s -H 'Content-Type: application/json' \
  -d '{"query":"{ greet }"}' http://127.0.0.1/graphql

The first query returns a JSON data.posts array of { id, title } objects fetched from the REST upstream and reshaped into GraphQL. The second returns {"data":{"greet":"Hello World!"}} from the offline @expr resolver, proving the gateway serves both remote and local fields.

Terminal showing a GraphQL query returning a posts array composed from the REST upstream and the greet resolver returning Hello World

Step 6: Compose across upstreams

The real power of Tailcall is stitching data together. In the starter config, Post.user is resolved with @call, so a single GraphQL query joins each post to its author, which lives at a different REST endpoint. You can also fetch a single record by argument.

curl -s -H 'Content-Type: application/json' \
  -d '{"query":"{ posts { id title user { id name email } } }"}' http://127.0.0.1/graphql | head -c 500; echo
curl -s -H 'Content-Type: application/json' \
  -d '{"query":"{ user(id: 1) { id name email } }"}' http://127.0.0.1/graphql

The first query returns posts with a nested user object joined from the users endpoint; the second returns a single user selected by the id argument. One GraphQL request, several upstream calls, batched and cached for you.

Terminal showing a GraphQL query joining each post to its author across two REST endpoints and a single user fetched by argument

Step 7: Replace the config with your own upstreams

The gateway is driven entirely by /etc/tailcall/config.graphql. Point the @http directives at your own REST endpoints, add types and fields, and use @grpc, @call and @expr as needed. Validate the config before restarting, so a typo never takes the gateway down.

sudo cat /etc/tailcall/config.graphql | sed -n '13,20p'
sudo -u tailcall /usr/local/bin/tailcall check /etc/tailcall/config.graphql

tailcall check reports Config ... ok when the schema is valid. After editing the file, apply it with a restart:

sudo systemctl restart tailcall
sudo systemctl is-active tailcall

See the Tailcall documentation at https://github.com/tailcallhq/tailcall for the full directive reference (@http, @grpc, @graphQL, @call, @expr, @server, @upstream).

Step 8: Enable HTTPS

nginx fronts the gateway on port 80 and is ready for TLS. Point a DNS record at the VM, then obtain a certificate with certbot and let it configure nginx for port 443.

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.com

After issuance, restrict the NSG so only 443 (and 22 from your management network) is reachable, and POST your queries to https://your-domain.example.com/graphql.

First boot, persistence and updates

The gateway binary and starter config live on the OS disk and are captured into the image, so a VM launched from this image is ready immediately. The tailcall-firstboot.service oneshot resolves this VM's endpoint URL, writes a root only info note at /root/tailcall-info.txt, then disables itself so subsequent reboots are unaffected. There is no credential to rotate: the shipped gateway fronts only a public demo API. The OS ships fully patched with unattended security upgrades enabled.

/usr/local/bin/tailcall --version
systemctl is-enabled tailcall nginx tailcall-firstboot.service
sudo sed -n '1,3p' /root/tailcall-info.txt; sudo grep -E '^TAILCALL_URL' /root/tailcall-info.txt
export DEBIAN_FRONTEND=noninteractive; sudo apt-get update -y >/dev/null 2>&1
echo "held packages: $(apt-mark showhold | tr '\n' ' ')[none]"

Terminal showing the Tailcall binary version, the enabled services, the per VM info note with the resolved endpoint URL, and a clean OS security baseline with no held packages

Support

Every cloudimg deployment includes 24/7 support with a 24 hour response SLA. Contact support@cloudimg.co.uk for help with deployment, configuration or scaling.

This is a repackaged open source software product with additional charges for cloudimg support services. Tailcall is a trademark of its respective owner. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.