Deployment guide

Sequin

Overview

This guide covers deploying and configuring Sequin on Ubuntu 24.04 on Azure, using SecureAnyCloud Azure Marketplace images.

Sequin is the open source change data capture platform for PostgreSQL. It attaches to a logical replication slot, reads your database's write-ahead log, and turns every insert, update and delete into a durable, ordered stream of change events. Sequin delivers the events to the destination of your choice. Instead of writing triggers, polling tables or building a bespoke outbox, you point Sequin at a database and a sink. Sequin handles replication, ordering, batching, retries, backfills and delivery observability.

The SecureAnyCloud image ships the free and open source, MIT-licensed Sequin server, run the officially supported way as the upstream container pinned by image digest. It runs alongside a bundled PostgreSQL 16 started with wal_level=logical and a bundled Redis 7. All three images are pinned by digest and captured into the VM, so your instance starts in seconds without pulling anything.

A CDC platform holds credentials for your production databases, so nothing ships with a known secret. Upstream Sequin's default admin@sequinstream.com login and open self-signup are both disabled. A unique console credential, admin password, Management API token, database password, Redis password and TLS certificate are generated for each VM on first boot, before anything is reachable.

Sequin is a trademark of its respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by Sequin Labs, Inc. It ships the free and open source, MIT-licensed, self-hosted software, unmodified.

What you get:

  • Sequin v0.14.6 (the MIT-licensed Sequin change data capture server and web console), pinned by image digest
  • A bundled PostgreSQL 16 started with wal_level=logical, pinned by image digest, reachable only inside a private Docker network (never published to a host port)
  • A bundled Redis 7 queue and cache, pinned by image digest, password protected and reachable only inside the private Docker network
  • Docker Engine with the Sequin application published to the loopback interface only, fronted by nginx with TLS on port 443
  • sequin.service, sequin-firstboot.service, sequin-postboot.service and nginx.service as systemd units, enabled and active on boot
  • A unique console credential, admin password, Management API token, PostgreSQL password, Redis password and self-signed TLS certificate generated per VM on first boot, never baked into the image
  • Upstream Sequin's default user and open self-signup both disabled, so there is no known login to change
  • A working demo change data capture pipeline out of the box, so you can see real captured changes within seconds of first boot
  • A clean, empty database on first boot: no default login, no shipped secret, no prior data
  • Ubuntu 24.04 LTS base with latest security patches applied at build time
  • Azure Linux Agent for seamless cloud integration and SSH key injection

Prerequisites

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

Recommended virtual machine size: Standard_D2s_v3 (2 vCPU, 8 GB RAM). Sequin runs on the Erlang virtual machine, and this image co-locates the server, PostgreSQL and Redis on one host. 8 GB is the sensible production starting point.

Standard_B2s (2 vCPU, 4 GB) works for evaluation and light workloads. For higher throughput or large backfills, use Standard_D4s_v3 or larger.

Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 443/tcp for the console and Management API from the networks that use them.

1

Deploy from the Azure Portal

Sign in to the Azure portal, choose Create a resource, search the Marketplace for Sequin by SecureAnyCloud, and select Create.

On Basics, 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 HTTPS (443).

Choose Review + create, then Create.

2

Deploy from the Azure CLI

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

Connect to your VM

ssh azureuser@<vm-ip>
4

Confirm the services are running

The stack runs as three containers (Sequin, PostgreSQL and Redis) under one sequin.service, fronted by nginx. Confirm that the services are active, list the containers, and check which ports are bound.

nginx listens on :80 (which redirects to HTTPS) and :443. The Sequin application is published to 127.0.0.1:7376 (loopback only), and PostgreSQL and Redis have no host port at all:

sudo systemctl is-active docker sequin nginx
sudo docker compose --env-file /etc/sequin/sequin.env -f /etc/sequin/compose.yaml ps
sudo ss -tln | grep -E ':80 |:443 |:7376'

All three services report active, and the three containers are running. nginx is bound on :80 and :443, and the Sequin application is bound only on 127.0.0.1:7376.

PostgreSQL (5432) and Redis (6379) never appear on a host port, because they are reachable only inside the private Docker network. This is deliberate: the only way in from the network is the authenticated nginx front door.

5

Read the per instance credentials

First boot generated a unique console credential, admin password, Management API token, database password and Redis password for this VM, before anything was reachable. It wrote them to a root-only file. Read the file:

sudo cat /root/sequin-credentials.txt

The file (mode 0600 root:root) holds CONSOLE_USER and CONSOLE_PASSWORD (the nginx credential that gates the web console), ADMIN_EMAIL and ADMIN_PASSWORD (Sequin's own login), the API_TOKEN you authenticate the Management API with, POSTGRES_PASSWORD, REDIS_PASSWORD, and SEQUIN_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 login to change.

6

Verify the security model

The health endpoint is public so that load balancers can probe it. The console requires the per-instance nginx credential, and the Management API requires the per-instance bearer token. Confirm all three behaviors:

SEQUIN_TOKEN=$(sudo grep '^API_TOKEN=' /root/sequin-credentials.txt | cut -d= -f2-)
SEQUIN_USER=$(sudo grep '^CONSOLE_USER=' /root/sequin-credentials.txt | cut -d= -f2-)
SEQUIN_PASS=$(sudo grep '^CONSOLE_PASSWORD=' /root/sequin-credentials.txt | cut -d= -f2-)
curl -sk -o /dev/null -w 'health (no auth): %{http_code}\n' https://localhost/health
curl -sk -o /dev/null -w 'console (no credential): %{http_code}\n' https://localhost/
curl -sk -o /dev/null -w 'console (per VM cred): %{http_code}\n' -u "$SEQUIN_USER:$SEQUIN_PASS" https://localhost/
curl -sk -o /dev/null -w 'API (no token): %{http_code}\n' https://localhost/api/sinks
curl -sk -o /dev/null -w 'API (per VM token): %{http_code}\n' -H "Authorization: Bearer $SEQUIN_TOKEN" https://localhost/api/sinks

The health check returns 200 with no authentication. The console returns 401 with no credential and 302 (a redirect to the Sequin login) once the per-instance credential is supplied, so reaching the login page at all requires a secret. The Management API returns 401 with no token and 200 with the per-instance token.

The Management API is not behind the nginx credential. HTTP Basic authentication and bearer tokens both use the Authorization header, so a client cannot present both at once. Sequin's own per-instance API token gates the API instead. Every path is authenticated by a per-instance secret except /health.

7

Open the web console

Browse to https://<vm-ip>/. Your browser warns about the self-signed certificate that first boot generated for this VM. Accept it, or replace the certificate with your own as described in Step 11.

You are prompted twice, by design. First, the nginx front door asks for CONSOLE_USER and CONSOLE_PASSWORD. Then Sequin's own login page asks for ADMIN_EMAIL and ADMIN_PASSWORD. Both pairs come from /root/sequin-credentials.txt.

After you sign in, the Sinks page opens. The image ships a working demo pipeline called demo-redis-stream, already Healthy and reading from the bundled demo-source database:

The Databases page shows the bundled PostgreSQL registered as a change data capture source, with its logical replication slot and publication:

8

Watch real changes being captured

The image ships a demo source database, sequin_demo, whose public.records table is published to the replication slot and streamed to the Redis stream sequin.records. Write a row and watch it arrive:

sudo docker compose --env-file /etc/sequin/sequin.env -f /etc/sequin/compose.yaml exec -T sequin_postgres \
  psql -U sequin -d sequin_demo -c "insert into public.records (name) values ('order-4711');" sleep 8
REDIS_PASS=$(sudo grep '^REDIS_PASSWORD=' /root/sequin-credentials.txt | cut -d= -f2-) sudo docker compose --env-file /etc/sequin/sequin.env -f /etc/sequin/compose.yaml exec -T sequin_redis \
  redis-cli --no-auth-warning -a "$REDIS_PASS" XREVRANGE sequin.records + - COUNT 1

The Redis stream entry contains the full change event: the record (your new row), the action (insert), the changes (for updates, the previous values) and metadata identifying the sink, database and table. That is the point of Sequin: your row change became a durable, ordered message in a stream, with no trigger and no polling.

Now check that the pipeline filters rather than forwarding everything. public.ignored_records exists in the same database but is deliberately not in the publication, so writes to it never reach the sink:

REDIS_PASS=$(sudo grep '^REDIS_PASSWORD=' /root/sequin-credentials.txt | cut -d= -f2-)
BEFORE=$(sudo docker compose --env-file /etc/sequin/sequin.env -f /etc/sequin/compose.yaml exec -T sequin_redis \
  redis-cli --no-auth-warning -a "$REDIS_PASS" XLEN sequin.records | tr -d '[:space:]')
sudo docker compose --env-file /etc/sequin/sequin.env -f /etc/sequin/compose.yaml exec -T sequin_postgres \
  psql -U sequin -d sequin_demo -c "insert into public.ignored_records (name) values ('never-streamed');" sleep 10
AFTER=$(sudo docker compose --env-file /etc/sequin/sequin.env -f /etc/sequin/compose.yaml exec -T sequin_redis \
  redis-cli --no-auth-warning -a "$REDIS_PASS" XLEN sequin.records | tr -d '[:space:]')
echo "stream length before=$BEFORE after=$AFTER (unpublished writes are correctly not delivered)"

The stream length is unchanged, because Sequin correctly did not deliver the write to the unpublished table.

The console shows the same result. Open the sink and its Messages tab to see the delivered change events, throughput and health:

9

Query the Management API

Everything in the console is also available over the Management API, authenticated with the per-instance bearer token. List the configured sinks and databases:

SEQUIN_TOKEN=$(sudo grep '^API_TOKEN=' /root/sequin-credentials.txt | cut -d= -f2-)
curl -sk -H "Authorization: Bearer $SEQUIN_TOKEN" https://localhost/api/sinks | head -c 400; echo
curl -sk -H "Authorization: Bearer $SEQUIN_TOKEN" https://localhost/api/postgres_databases | head -c 400; echo

Use the same token from your own machine by replacing localhost with your VM address, for example https://<vm-ip>/api/sinks. Treat the token as a production secret: it grants full control of every pipeline on this instance.

10

Connect your own database

The demo pipeline makes the image useful immediately, but the real workflow is to point Sequin at your own PostgreSQL. Sequin requires the source database to have wal_level = logical, plus a replication slot and a publication.

On your own database, run the following as a superuser. This is a template, not something to run on this VM:

ALTER SYSTEM SET wal_level = logical; -- restart PostgreSQL for wal_level to take effect CREATE PUBLICATION sequin_pub FOR TABLE public.orders, public.customers; SELECT pg_create_logical_replication_slot('sequin_slot', 'pgoutput');

In the console, choose Databases, then Connect Database. Supply the host, port, database, user and password along with the slot and publication names.

Once the database is connected, choose Sinks, then Create Sink. Pick a destination: Kafka, SQS, SNS, Kinesis, NATS, RabbitMQ, Redis Streams, GCP Pub/Sub, Azure Event Hubs, Elasticsearch, Typesense, Meilisearch, an HTTP webhook, or Sequin's own pull API. Then select the tables and actions to stream. You can also run an initial backfill so that the sink starts from the current contents of the table rather than only future changes.

You can also declare pipelines as configuration. Sequin reads a YAML document describing accounts, databases and sinks, which is how this image provisions its own demo pipeline:

databases: - name: "production" hostname: "db.internal" port: 5432 database: "app" username: "sequin" password: "..." slot: name: "sequin_slot" publication: name: "sequin_pub" sinks: - name: "orders-to-kafka" database: "production" source: include_tables: - "public.orders" actions: [insert, update, delete] destination: type: "kafka" hosts: "kafka.internal:9092" topic: "orders"
11

Use your own certificate and domain (production)

The image generates a self-signed TLS certificate for the VM's own IP on first boot, so the console is encrypted from the first request with no configuration. For production, point a DNS name at the VM and install a real certificate.

Replace /etc/sequin/tls/sequin.crt and /etc/sequin/tls/sequin.key with your certificate and private key (keep the key 0600 root:root), then reload nginx.

Also set SERVER_HOST in /etc/sequin/sequin.env to your DNS name so that the console generates correct absolute links, and restart the stack.

You can rotate the console credential at any time with htpasswd -B /etc/sequin/console.htpasswd SecureAnyCloud. Change the Sequin admin password from the console under account settings.

12

Server components

The image contains these components:

Sequin: v0.14.6, upstream container pinned by digest, published to 127.0.0.1:7376.

PostgreSQL: version 16 (alpine), pinned by digest, wal_level=logical, private Docker network only.

Redis: version 7 (alpine), pinned by digest, password protected, private Docker network only.

Front door: nginx, TLS on 443, port 80 redirects, per-VM bcrypt credential on the console.

Config: /etc/sequin/compose.yaml, with per-VM secrets in /etc/sequin/sequin.env (0600).

Credentials: /root/sequin-credentials.txt (0600 root:root).

Data: Docker named volumes sequin_pgdata and sequin_redisdata.

Licence: MIT.

13

Managing the Sequin service

sudo systemctl status sequin --no-pager | head -20 sudo docker compose --env-file /etc/sequin/sequin.env -f /etc/sequin/compose.yaml ps
sudo docker compose --env-file /etc/sequin/sequin.env -f /etc/sequin/compose.yaml logs --tail 20 sequin

Use sudo systemctl restart sequin to restart the whole stack (the unit runs docker compose up -d --wait), and sudo systemctl stop sequin to bring it down.

The named volumes persist across restarts, so your pipelines, connected databases and replication progress survive.

14

Security recommendations

  • Restrict 443/tcp in the NSG to the networks that genuinely need the console and Management API, and 22/tcp to your management network only
  • Replace the self-signed certificate with a certificate for a real DNS name (Step 11)
  • Treat /root/sequin-credentials.txt as a production secret. Copy the values into your secret manager and consider removing the file afterwards
  • Rotate the Management API token from the console if it is ever shared, and rotate the console credential with htpasswd
  • Give Sequin a dedicated PostgreSQL role on each source database with REPLICATION and read access to the published tables only, never a superuser
  • Keep the VM patched. Unattended security upgrades are enabled on this image
15

Support and Licensing

Sequin is licensed under the MIT License. This image bundles the unmodified upstream software and is produced by SecureAnyCloud, which is not affiliated with or endorsed by Sequin Labs, Inc.

Deploy on Azure

Find Sequin on Ubuntu 24.04 LTS by SecureAnyCloud on the Azure Marketplace.

Need Help?

SecureAnyCloud provides 24/7 support for this image. Contact us at secureanycloud.com.

Deploy on Azure

Launch Sequin 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