Deployment guide

Outpost

Overview

Hookdeck Outpost is open source infrastructure for outbound webhooks and event delivery. It exposes a Bearer-authenticated REST API that routes, signs, queues, retries and delivers the events your application sends to your users' destinations.

This SecureAnyCloud image runs Outpost on Ubuntu 24.04 on Azure, with its PostgreSQL log store, Valkey entity store and RabbitMQ message queue already bundled. You create tenants, register their destinations (webhooks, message queues and more), and publish events through the API. Outpost handles signing, fan-out, delivery, retries and delivery logs.

Hookdeck and Outpost are trademarks of their respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by Hookdeck. It ships the free and open source, Apache 2.0 licensed, self-hosted software, unmodified.

What you get:

  • Hookdeck Outpost v1.0.7 (the Apache 2.0 licensed Outpost Go service), run as its api, delivery and log workers from the upstream containers, pinned by image digest
  • A bundled PostgreSQL 16 log store, pinned by image digest, reachable only inside a private Docker network (never published to a host port)
  • A bundled Valkey 8 entity store, pinned by image digest, password protected and reachable only inside the private Docker network
  • A bundled RabbitMQ 3 message queue, pinned by image digest, password protected and reachable only inside the private Docker network
  • All container images captured into the VM, so your instance starts in seconds
  • Docker Engine with the Outpost API published to the loopback interface only, fronted by nginx on port 80
  • outpost.service, outpost-firstboot.service, outpost-postboot.service and nginx.service as systemd units, enabled and active on boot
  • A unique admin API key, JWT signing secret, encryption secret and PostgreSQL, Valkey and RabbitMQ passwords, generated for each VM on first boot before the port is reachable and never baked into the image
  • Clean, empty datastores on first boot: no default login, no shipped secret, no prior data
  • Ubuntu 24.04 LTS base with the latest security patches applied at build time
  • Azure Linux Agent for cloud integration and SSH key injection
  • 24/7 SecureAnyCloud support with a guaranteed 24 hour response SLA

Prerequisites

  • An active Azure subscription, an SSH public key, and a VNet with a subnet in the target region
  • A subscription to the Hookdeck Outpost listing on Azure Marketplace

Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point. For higher event volumes, use Standard_D2s_v5 or larger.

Network security group (NSG) inbound rules: allow 22/tcp from your management network, and 80/tcp for the API from the networks that call it.

1

Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Hookdeck Outpost by SecureAnyCloud, and select Create.

On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80).

Then select Review + create and Create.

2

Deploy from the Azure CLI

RG="outpost-prod"; LOCATION="eastus"; VM_NAME="outpost-01" IMAGE_URN="sac1647283583153:outpost-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name outpost-vnet --address-prefix 10.100.0.0/16 --subnet-name outpost-subnet --subnet-prefix 10.100.1.0/24 az network nsg create -g "$RG" --name outpost-nsg az network nsg rule create -g "$RG" --nsg-name outpost-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 outpost-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:outpost-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name outpost-vnet --subnet outpost-subnet --nsg outpost-nsg --public-ip-sku Standard
3

Connect to your VM

ssh azureuser@<vm-ip>
4

Confirm the services are running

The stack runs the Outpost api, delivery and log workers plus the bundled PostgreSQL, Valkey and RabbitMQ, all under one outpost.service and fronted by nginx.

Confirm that the services are active, list the containers, and check which ports are bound. nginx listens on :80. The Outpost API is published to 127.0.0.1:3333 (loopback only). The datastores have no host port at all.

sudo systemctl is-active docker outpost nginx
sudo docker compose -f /etc/outpost/compose.yaml ps
sudo ss -tlnp | grep -E ':80 |:3333'

All three services report active, and the containers show Up (with PostgreSQL, Valkey and RabbitMQ healthy). nginx is bound on :80, and the Outpost API is bound only on 127.0.0.1:3333.

PostgreSQL (5432), Valkey (6379) and RabbitMQ (5672) never appear on a host port, because they are reachable only inside the private Docker network.

5

Read the per-instance credentials

On first boot, before the port became reachable, this VM generated a unique admin API key, JWT signing secret, encryption secret and datastore passwords. They are written to a root-only file. Read it as root:

sudo cat /root/outpost-credentials.txt

The file (mode 0600 root:root) holds API_KEY (the admin key you authenticate the API with), API_JWT_SECRET, AES_ENCRYPTION_SECRET, POSTGRES_PASSWORD, REDIS_PASSWORD, RABBITMQ_PASSWORD and OUTPOST_URL (the address to reach this VM on).

None of these values ship in the image. Every value is unique to this VM, so no two instances share a credential and there is no default key to change.

6

Verify the API security model

The health endpoint is public, but every admin endpoint requires the Bearer key. Run the following calls to confirm that an unauthenticated request is rejected with 401, while the per-instance key authenticates a real call:

API_KEY=$(sudo grep '^API_KEY=' /root/outpost-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'healthz (no auth): %{http_code}\n' http://localhost/api/v1/healthz
curl -s -o /dev/null -w 'tenants (no key): %{http_code}\n' http://localhost/api/v1/tenants
curl -s -X PUT -H "Authorization: Bearer $API_KEY" http://localhost/api/v1/tenants/acme >/dev/null
curl -s -o /dev/null -w 'tenant/acme (with key): %{http_code}\n' -H "Authorization: Bearer $API_KEY" http://localhost/api/v1/tenants/acme

The health check returns 200 with no authentication. The unauthenticated tenants call is rejected with 401. Reading the tenant back with the per-instance key returns 200.

That is the whole security model: the health probe is open for load balancers, and everything else is gated by the admin API key.

7

Publish your first event

The core workflow has three parts. Create a tenant (one per customer or team), register its destinations (the webhooks and queues Outpost delivers to), then publish events.

Outpost routes each event to every matching destination, signs it, queues it and delivers it, retrying with backoff on failure. The image ships with a wildcard topic (*), so destinations subscribed to * receive every event out of the box.

Run the whole flow from the VM:

API_KEY=$(sudo grep '^API_KEY=' /root/outpost-credentials.txt | cut -d= -f2-)
curl -s -X PUT http://localhost/api/v1/tenants/orders-team \
  -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
  -d '{"metadata":{"name":"Orders team"}}' echo
curl -s -X POST http://localhost/api/v1/tenants/orders-team/destinations \
  -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
  -d '{"type":"webhook","topics":["*"],"config":{"url":"https://example.com/webhook"}}' echo
curl -s -X POST http://localhost/api/v1/publish \
  -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
  -d '{"tenant_id":"orders-team","topic":"*","eligible_for_retry":true,"data":{"order_id":"ord_123","amount":4200}}' echo

Each call returns the created object as JSON. The destination call returns a webhook id and a signing secret. The final POST /api/v1/publish returns an event id and the destination_ids it was delivered to.

At that point Outpost has accepted the event, signed it, and queued it for delivery to the https://example.com/webhook destination with automatic retries. Point the destination URL at a real receiver of yours (for a quick test, a request bin) and the signed delivery arrives there.

8

Call the API from your own machine

From any machine that can reach the VM on port 80, call the API with the admin API key and the VM public IP. Retrieve the key once over SSH, then call the API with your VM address:

curl -H "Authorization: Bearer <API_KEY>" http://<vm-ip>/api/v1/tenants/acme

For production, use the official Outpost SDKs (available for Go, Python and TypeScript). Set the SDK's base URL to http://<vm-ip>/ (or your own domain, see Step 10) and pass the admin API key.

You can then manage tenants, destinations and events from your application code, exactly as the curl calls above do.

9

Configure your own event topics

The image ships with a wildcard topic (*), so you can publish and subscribe immediately. To use your own named event topics (for example user.created or order.shipped), set the comma-separated TOPICS list in the per-instance environment file, then restart the stack:

sudo sed -i 's/^TOPICS=.*/TOPICS=user.created,user.updated,order.shipped/' /etc/outpost/outpost.env
sudo systemctl restart outpost

Destinations can then subscribe to specific topics ("topics":["user.created"]) or to all of them ("topics":["*"]), and you publish events on those topic names.

To view the currently valid topics at any time, run curl -H "Authorization: Bearer $API_KEY" http://localhost/api/v1/topics.

10

Use your own domain and HTTPS (production)

The image serves the API over plain HTTP on port 80, which suits a load balancer or private network. For production, terminate TLS in front of Outpost so that keys and payloads travel encrypted. You have two options:

  • Front the VM with Azure Application Gateway, a load balancer with a managed certificate, or a CDN, and forward to nginx on port 80.
  • Install your own certificate: add a TLS server block to /etc/nginx/sites-available/outpost that references your certificate and key, open 443/tcp on the NSG, then run sudo systemctl reload nginx.

Then point a DNS name you control at the VM public IP. Set OUTPOST_URL in /etc/outpost/outpost.env to https://your-domain/, and use that name as the base URL in your Outpost SDKs.

11

Server components

This image is built from the following components.

  • Event delivery service: Hookdeck Outpost v1.0.7 (Go binary, pinned by image digest)
  • Workers: Outpost api, delivery and log services from one image
  • API front: nginx on :80, reverse proxying to 127.0.0.1:3333
  • Log store: bundled PostgreSQL 16 (private Docker network, no host port)
  • Entity store: bundled Valkey 8 (private Docker network, no host port, password protected)
  • Message queue: bundled RabbitMQ 3 (private Docker network, no host port, password protected)
  • Orchestration: Docker Compose under outpost.service
  • Admin API key: per instance, generated on first boot into /etc/outpost/outpost.env
  • Credentials file: per instance, written on first boot to /root/outpost-credentials.txt
  • Operating system: Ubuntu 24.04 LTS (patched at build time)
  • License: Apache 2.0 (Outpost)
12

Managing the Outpost service

sudo systemctl status outpost --no-pager | head -12 sudo docker compose -f /etc/outpost/compose.yaml logs --tail 40 api

Restart the whole stack with sudo systemctl restart outpost. Follow the API logs live with sudo docker compose -f /etc/outpost/compose.yaml logs -f api.

Configuration lives in /etc/outpost/outpost.env and /etc/outpost/compose.yaml. After you change the environment file, restart the service to apply it.

13

Security recommendations

  • Restrict the NSG. Allow TCP 80 (and 22 for administration) only from the networks that call the API.
  • Terminate TLS in front of Outpost (Step 10), so the Bearer key and payloads are encrypted in transit.
  • Treat the API key like a password. It is stored in /root/outpost-credentials.txt (root only). If it is ever exposed, rotate API_KEY in /etc/outpost/outpost.env and restart the service.
  • Keep the bundled datastores private. PostgreSQL, Valkey and RabbitMQ sit on a private Docker network with no host port. Keep them that way.
  • Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
14

Support and Licensing

Outpost is distributed under the Apache License 2.0. This SecureAnyCloud image bundles the unmodified official open source Outpost release.

SecureAnyCloud provides the packaging, the bundled PostgreSQL, Valkey and RabbitMQ, the per-instance secret generation, this deployment guide, and 24/7 support with a guaranteed 24 hour response SLA.

Outpost is an independent open source project by Hookdeck. This image is not affiliated with or endorsed by Hookdeck.

Deploy on Azure

Find Hookdeck Outpost on Ubuntu 24.04 in the Azure Marketplace, published by SecureAnyCloud. Deploy from the Portal or the Azure CLI as shown above.

Need Help?

Email sales@secureanycloud.com for deployment help, configuration questions or licensing enquiries.

Deploy on Azure

Launch Hookdeck Outpost on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure