Deployment guide

Apache Iggy 0.9.0

Overview

Apache Iggy is a persistent message streaming platform written in Rust. Producers append messages to topics, which are grouped into streams and split into partitions. Consumers read the messages back by offset, timestamp or consumer group.

Iggy keeps every message on disk in an append-only log and serves it with very low latency over its binary protocol. It has client SDKs for Rust, Python, Go, Java, C#, Node.js and C++, plus an HTTP REST API and a web console.

This image runs Apache Iggy 0.9.0 on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud streaming and messaging listing on AWS Marketplace, and you pick it as the software version when you subscribe and launch.

What you get

  • No default login. The iggy root password is generated on your instance at first boot and written to /root/apache-iggy-credentials.txt (root only). The upstream development login iggy/iggy is never set, and the server will not start until your instance's own password exists.
  • TLS everywhere. nginx serves the web console and the REST API on port 443 with a certificate generated on your instance, and port 80 only redirects to HTTPS. SDK clients connect to port 8090, Iggy's own TLS listener, with the same certificate. Plaintext TCP, QUIC and the WebSocket transport are switched off.
  • Fail closed. Before every start, a check refuses to run the server in any of these cases: no root password for your instance, a short or well-known password, plaintext TCP, QUIC, the WebSocket transport or open CORS switched on, or a certificate that is not a server certificate. nginx opens 80 and 443 only after the first boot has proven that your password works and that iggy/iggy is refused.
  • A locked-down container. Iggy runs as the official apache/iggy:0.9.0 image, pinned by its digest, under systemd. It runs as an unprivileged user, without Linux capabilities, with a read-only root filesystem, and under Docker's default seccomp profile plus only the three io_uring system calls the server needs. Its HTTP port is published on the loopback address only.
  • Hardened defaults. The container and every host service except the one that reads the instance's name are blocked from the instance metadata service, and the AMI requires IMDSv2. nginx hands Iggy the configured host name and the real client address, never values a client sends. Requests carrying another site's Origin are refused, logins are rate limited per client address, and the console's session cookie is Secure and SameSite=Strict.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
  • 24/7 SecureAnyCloud support.

What is on the image: Apache Iggy 0.9.0 (the official Apache image apache/iggy:0.9.0, with the server, its embedded web console and the iggy command-line client), Docker Engine (Docker CE), nginx 1.24, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

Before you deploy, make sure you have:

  • An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with a route to the internet or a private route that you use for management.
  • A security group with these inbound rules:
    TCP 22 (SSH) from your admin network.
    TCP 443 (the web console and REST API) from your admin network and the hosts that call the API.
    TCP 8090 (the SDK transport) from the networks your applications run in.
    TCP 80, if you want plain HTTP requests redirected to HTTPS.

Never open 3000. The HTTP API listens on the loopback address only.

1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Apache Iggy 0.9.0 on Ubuntu 24.04 as the software version, choose your Region, then choose Launch through EC2.

Choose m5.large, your key pair and a security group as described above. Iggy keeps its message log on the root volume, so give the instance a root volume sized for the messages you expect to retain (Step 13).

To launch with the AWS CLI instead, replace the values in angle brackets:

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=iggy}]'

First boot takes about a minute after the instance is running. The boot services name the instance, create its certificate and root password, start Iggy and prove the login. Then they open nginx.

2

Connect to your instance

For the Apache Iggy 0.9.0 on Ubuntu 24.04 version, the SSH user is ubuntu. Connect with the key pair you chose:

ssh -i <your-key.pem> ubuntu@<public-dns-name>
3

Check the services

systemctl is-active apache-iggy-hostname apache-iggy-firstboot iggy-server apache-iggy-postboot nginx
sudo test -f /var/lib/sac/data/apache-iggy-firstboot.done && echo FIRSTBOOT_DONE

All five units are active, and the first-boot marker is present:

active active active active active FIRSTBOOT_DONE

Only SSH, nginx and the TLS SDK port listen on network interfaces. The HTTP API listens on the loopback address:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd")) 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) 0.0.0.0:8090 users:(("docker-proxy")) 127.0.0.1:3000 users:(("docker-proxy")) [::]:22 users:(("sshd"),("systemd")) [::]:443 users:(("nginx"),("nginx"),("nginx")) [::]:80 users:(("nginx"),("nginx"),("nginx")) [::]:8090 users:(("docker-proxy"))
4

Get your URLs, password and certificate fingerprint

sudo cat /root/apache-iggy-credentials.txt

The file is readable by root only:

# Apache Iggy 0.9.0 - generated on this instance's first boot (unique to this instance). # Keep this file safe. The root user can do everything; create least-privilege users for your applications. IGGY_WEB_CONSOLE_URL=https://<public-dns-name>/ui/ IGGY_HTTP_API_URL=https://<public-dns-name>/ IGGY_TCP_TLS_ADDRESS=<public-dns-name>:8090 IGGY_TLS_DOMAIN=<public-dns-name> IGGY_TLS_CERT_SHA256=<the certificate's SHA-256 fingerprint> IGGY_ROOT_USERNAME=iggy IGGY_ROOT_PASSWORD=<IGGY_ROOT_PASSWORD>
#
# The certificate is this instance's own self-signed END-ENTITY certificate (/etc/iggy/tls/iggy.crt). Compare its # SHA-256 fingerprint above with what your browser or client shows, or replace it with a CA-issued certificate # (see the guide). SDK clients connect to IGGY_TCP_TLS_ADDRESS with TLS on, IGGY_TLS_DOMAIN as the TLS domain and a # copy of /etc/iggy/tls/iggy.crt as the CA file. Plaintext TCP, QUIC and WebSocket transports are off. # On-instance CLI (prompts for the password):
sudo iggy -u iggy stream list

The name is the instance's public DNS name from the EC2 instance metadata, or its public IP address, or its private address when it has no public one. The certificate also names the public and private IP addresses. If you stop and start the instance without an Elastic IP, the boot service picks up the new name and issues a new certificate for it. To serve Iggy under your own DNS name or with your own certificate, see Step 11.

The certificate is a self-signed server certificate created on your instance. It cannot sign other certificates, so do not add it to your operating system's or browser's trust store. Instead, compare its fingerprint with the one in the credentials file:

openssl x509 -in /etc/iggy/tls/iggy.crt -noout -fingerprint -sha256
5

The web console

Open https://<public-dns-name>/ui/ in a browser. The certificate is self-signed, so the browser warns you the first time. Open the certificate details, check that its SHA-256 fingerprint matches IGGY_TLS_CERT_SHA256, and proceed. Alternatively, install a certificate from your CA first (Step 11). Sign in as iggy with the password from the credentials file.

  • The overview shows the server's resource use and totals: streams, topics, partitions, segments, messages, connected clients and consumer groups.
  • The streams view lists every stream. Select one to see its topics with their message counts, partitions and message expiry. You can also create topics there, and open a topic to see its partitions.
  • Open a partition to browse its messages. The console shows each payload base64 encoded, with its offset, timestamp and checksum.
6

Streams, topics and messages from the instance

The iggy command runs the command-line client from the same pinned Iggy image. It runs as a throwaway unprivileged container next to the server and talks to the local HTTP API. Without -p it reads the password from the terminal, or from standard input as in the example below, so the password never appears on a command line.

Create a stream and a topic with one partition whose messages expire after seven days. Then send three messages and read them back:

P() {
sudo sed -n 's/^IGGY_ROOT_PASSWORD=//p' /root/apache-iggy-credentials.txt; } P | sudo iggy -u iggy stream create orders 2>&1 | grep -v keyring P | sudo iggy -u iggy topic create orders events 1 none 7days 2>&1 | grep -v keyring P | sudo iggy -u iggy message send orders events "order-1001 created" "order-1002 created" "order-1003 shipped" 2>&1 | grep -v keyring P | sudo iggy -u iggy message poll --first --message-count 10 orders events 0 2>&1 | grep -v keyring
Executing create stream with name: orders and ID auto incremented Stream with name: orders and ID auto incremented created Executing create topic with name: events, message expiry: 7days, compression algorithm: none, max topic size: server_default in stream with ID: orders Topic with name: events, partitions count: 1, compression algorithm: none, message expiry: 7days, max topic size: server_default created in stream with ID: orders Executing send messages to topic with ID: events and stream with ID: orders Sent messages to topic with ID: events and stream with ID: orders Executing poll messages from topic ID: events and stream with ID: orders Polled messages from topic with ID: events and stream with ID: orders (from partition with ID: 0) Polled 3 messages of total size 246 B, it took <time> +--------+----------------------------+-----------------------------------------+--------+--------------------+ | Offset | Timestamp | ID | Length | Payload | +=============================================================================================================+ | 0 | <timestamp> | <message id> | 18 | order-1001 created | |--------+----------------------------+-----------------------------------------+--------+--------------------| | 1 | <timestamp> | <message id> | 18 | order-1002 created | |--------+----------------------------+-----------------------------------------+--------+--------------------| | 2 | <timestamp> | <message id> | 18 | order-1003 shipped | +--------+----------------------------+-----------------------------------------+--------+--------------------+

The client prints two keyring backend unavailable warnings on every run because it has no desktop keyring to keep a session in. They are harmless, and the grep -v keyring above hides them.

In an interactive shell, run sudo iggy -u iggy stream list and type the password at the prompt. Run sudo iggy --help to list every command, including streams, topics, partitions, consumer groups, users and personal access tokens.

7

The HTTP REST API

Every console action goes through Iggy's REST API, served at https://<public-dns-name>/. Log in with POST /users/login to get a bearer token that is valid for one hour. Then send the token in the Authorization header. The API never reads credentials from cookies.

Keep the password and the token in files readable only by you, never on the command line. On the instance, the --connect-to option sends the request to nginx on this host while the certificate and host name are still checked:

H=$(sudo sed -n 's/^IGGY_TLS_DOMAIN=//p' /root/apache-iggy-credentials.txt) umask 077;
W=$(mktemp -d) sudo sed -n 's/^IGGY_ROOT_PASSWORD=//p' /root/apache-iggy-credentials.txt | { read -r P; printf '{"username":"iggy","password":"%s"}' "$P" > "$W/login.json"; } curl -s --cacert /etc/iggy/tls/iggy.crt --connect-to "$H:443:127.0.0.1:443" -H 'Content-Type: application/json' \
  --data @"$W/login.json" "https://$H/users/login" | jq -r .access_token.token | { read -r T; printf 'header = "Authorization: Bearer %s"\n' "$T" > "$W/auth"; } curl -s --cacert /etc/iggy/tls/iggy.crt --connect-to "$H:443:127.0.0.1:443" -K "$W/auth" "https://$H/stats" | jq -c '{iggy_server_version, streams_count, messages_count}' curl -s --cacert /etc/iggy/tls/iggy.crt --connect-to "$H:443:127.0.0.1:443" -K "$W/auth" "https://$H/streams" | jq -c '.[] | {id, name, topics_count, messages_count}' rm -rf "$W"
{"iggy_server_version":"0.9.0","streams_count":1,"messages_count":3} {"id":0,"name":"orders","topics_count":1,"messages_count":3}

From another machine, copy the certificate first (Step 8) and use curl --cacert iggy.crt https://<public-dns-name>/....

The API responds to bad requests as follows: a wrong password gets HTTP 400 with "code":"invalid_credentials", a request without a token gets HTTP 401 with "code":"access_token_missing", and a browser request from another site gets HTTP 403. nginx allows 10 logins per minute per client address, in bursts of 5, so a script cannot be used to guess passwords.

8

Connect your applications over TLS

Applications use an Iggy SDK over the TCP transport on port 8090, which accepts TLS only. The client checks the server certificate, so give it a copy of the instance's certificate as its CA file. This trusts that one certificate for that one connection, so do not install it in a system trust store.

Copy the certificate to the client machine and compare its fingerprint with IGGY_TLS_CERT_SHA256 from the credentials file:

scp -i <your-key.pem> ubuntu@<public-dns-name>:/etc/iggy/tls/iggy.crt iggy.crt openssl x509 -in iggy.crt -noout -fingerprint -sha256

Use these connection settings for any SDK: server address <public-dns-name>:8090, TLS on, TLS domain <public-dns-name> (the IGGY_TLS_DOMAIN value, which the certificate names), CA file iggy.crt, and a user and password. Use the root user, or better, a user you create in Step 9.

This example is a Python client that uses the official SDK (pip install apache-iggy==0.9.0):

import asyncio, os from apache_iggy import IggyClient, TcpConfig, SendMessage, PollingStrategy, Consumer HOST = "<public-dns-name>" async def main(): client = IggyClient(TcpConfig(server_address=f"{HOST}:8090", tls_enabled=True, tls_domain=HOST, tls_ca_file="iggy.crt")) await client.connect() await client.login_user("orders-app", os.environ["IGGY_PASSWORD"]) await client.send_messages("orders", "events", 0, [SendMessage("order-1004 created")]) msgs = await client.poll_messages("orders", "events", consumer=Consumer.Single("billing"), polling_strategy=PollingStrategy.Next(), count=10, auto_commit=True) for m in msgs: print(m.offset(), bytes(m.payload()).decode()) asyncio.run(main())

The Rust, Go, Java, C# and Node.js SDKs have the same three TLS settings in their TCP client configuration: TLS enabled, TLS domain and CA file. A client without the CA file is refused by its own certificate check, and the server disconnects a plaintext client.

Clients inside the VPC can connect to the private IP address, which the certificate also names. Set the TLS domain to whatever address you connect to, or set your own DNS name first (Step 11).

9

Users, permissions and tokens

Give each application its own user with only the permissions it needs, instead of sharing the root password. You create users through the API with a JSON body.

The block below generates a random password for an orders-app user into ~/orders-app.password, which only you can read. It then creates the user with permission to read the orders stream (stream id 0) and to send and poll its messages, and nothing else:

H=$(sudo sed -n 's/^IGGY_TLS_DOMAIN=//p' /root/apache-iggy-credentials.txt) C=(curl -s --cacert /etc/iggy/tls/iggy.crt --connect-to "$H:443:127.0.0.1:443") umask 077;
W=$(mktemp -d) sudo sed -n 's/^IGGY_ROOT_PASSWORD=//p' /root/apache-iggy-credentials.txt | { read -r P; printf '{"username":"iggy","password":"%s"}' "$P" > "$W/login.json"; } "${C[@]}" -H 'Content-Type: application/json' --data @"$W/login.json" "https://$H/users/login" | jq -r .access_token.token | { read -r T; printf 'header = "Authorization: Bearer %s"\n' "$T" > "$W/auth"; } openssl rand -base64 32 | tr -dc 'A-Za-z0-9' > ~/orders-app.password jq -n --rawfile p ~/orders-app.password '{username: "orders-app", password: ($p | rtrimstr("\n")), status: "active", permissions: { global: {manage_servers: false, read_servers: false, manage_users: false, read_users: false, manage_streams: false, read_streams: false, manage_topics: false, read_topics: false, poll_messages: false, send_messages: false}, streams: {"0": {manage_stream: false, read_stream: true, manage_topics: false, read_topics: true, poll_messages: true, send_messages: true}}}}' > "$W/user.json" "${C[@]}" -K "$W/auth" -H 'Content-Type: application/json' --data @"$W/user.json" -o /dev/null -w 'create user: HTTP %{http_code}\n' "https://$H/users" "${C[@]}" -K "$W/auth" "https://$H/users" | jq -c '.[] | {id, username, status}' rm -rf "$W"
create user: HTTP 200 {"id":0,"username":"iggy","status":"active"} {"id":1,"username":"orders-app","status":"active"}

The orders-app user can now read the orders stream and send and poll its messages. It gets HTTP 403 ("code":"unauthorized") if it tries to create a stream. Copy ~/orders-app.password to the application's secret store, then delete it from the instance.

Users can also create personal access tokens (POST /personal-access-tokens with a name and an expiry) for services that should not hold a password. A token is shown once and can be revoked at any time. See the Iggy documentation for the full permission model.

10

Changing the root password

Change the root password with the helper below. It sets a new random password through the API. Then it updates /etc/iggy/secrets/root.env, which the server reads at startup, and the credentials file to match:

sudo apache-iggy-rotate-root-password
apache-iggy-rotate-root-password: done (new password in /root/apache-iggy-credentials.txt)

If you change the root password another way, such as through an SDK or the API, the server keeps the new password. The credentials file and root.env still hold the old value, and running the helper afterwards needs the current password in the credentials file.

11

Your own DNS name and certificate

Point a DNS record at the instance, then tell Iggy to serve that name. The command issues a new self-signed certificate that names it, used by both nginx and the SDK port. It also updates nginx and the allowed browser origin, sets the address Iggy advertises to clients, updates the credentials file and restarts the server:

sudo apache-iggy-set-hostname iggy.example.com

The name is saved in /etc/sac/apache-iggy-hostname and survives reboots and address changes.

To use a certificate from your own CA or from Let's Encrypt for both the console and the SDK port, copy the certificate chain and its private key to the instance. Put the server certificate first in the chain, followed by any intermediates. Then run:

sudo apache-iggy-install-cert fullchain.pem privkey.pem iggy.example.com

The helper checks that the certificate is a server certificate for that name, matches the key and is not about to expire. It then installs both for nginx and Iggy, and restarts them.

The boot-time renewal leaves an installed certificate alone. Clients then verify it with their normal CA certificates and need no CA file. Run apache-iggy-set-hostname again to go back to a self-signed certificate. To renew a CA certificate, run apache-iggy-install-cert again with the new files.

12

Security checks

Run apache-iggy-selfcheck to re-prove the appliance with this instance's credentials. Every request goes through nginx over HTTPS and is verified against the instance's certificate, as a client would see it:

sudo apache-iggy-selfcheck
SELFCHECK OK root login over verified HTTPS, iggy/iggy + wrong password + anonymous + cross-origin refused, TCP 8090 is TLS for <public-dns-name>

The metadata block is a firewall rule for the container (DOCKER-USER chain) plus a systemd IPAddressDeny rule on each host service. You can inspect both:

sudo iptables -S DOCKER-USER for u in iggy-server nginx apache-iggy-firstboot apache-iggy-postboot docker; do printf '%s: ' $u; systemctl show -p IPAddressDeny --value $u.service; done
-N DOCKER-USER -A DOCKER-USER -d 168.63.129.16/32 -j DROP -A DOCKER-USER -d 169.254.169.254/32 -j DROP iggy-server: 168.63.129.16/32 169.254.169.254/32 nginx: 168.63.129.16/32 169.254.169.254/32 apache-iggy-firstboot: 168.63.129.16/32 169.254.169.254/32 apache-iggy-postboot: 168.63.129.16/32 169.254.169.254/32 docker: 168.63.129.16/32 169.254.169.254/32

Only apache-iggy-hostname, which reads the instance's name and addresses at boot, can reach the metadata service. Because of the block, Iggy and any connectors you run in containers on this host cannot pick up the instance's IAM role credentials.

13

Versions, provenance and file locations

sed -n '1,3p' /usr/share/doc/apache-iggy/BUILD-PROVENANCE.txt
sudo docker ps --format '{{.Names}} {{.Image}} {{.Status}}'
Apache Iggy 0.9.0 (sac AWS image) Server image: apache/iggy@sha256:b35fc2841964f2962a5f164d33b3ae3a9de99a66920defd3cd8a1c039470602d (Docker Hub apache/iggy:0.9.0, published 2026-09-18 with the server-0.9.0 release) Licence: Apache-2.0. LICENSE sha256 f4b9e8594f90b3730c8c856887c43469eae52e397de0c66871211c6ef577c10d = LICENSE in iggy-0.9.0-src.tar.gz iggy-server b35fc2841964 Up <time>

These are the important files and directories on the instance:

  • /var/lib/iggy: Iggy's data, including streams, topics, partitions, segments and the metadata journal (users and permissions).
  • /etc/iggy/server.env: the server settings, as Iggy environment variables. /etc/iggy/node.env holds the advertised address.
  • /etc/iggy/secrets/root.env: the root user name and password that the server reads at startup. Only root and the server can read it.
  • /etc/iggy/tls/iggy.crt and iggy.key: the certificate (public) and its private key, used by nginx and by Iggy on port 8090.
  • /etc/iggy/seccomp-iggy.json: the container's seccomp profile (Docker's default plus io_uring).
  • /root/apache-iggy-credentials.txt: URLs, root password and certificate fingerprint. Only root can read it.
  • /etc/nginx/sites-available/sac-iggy: the HTTPS front door for the console and the REST API.
  • /usr/share/doc/apache-iggy/: LICENSE, NOTICE, LICENSE-binary and BUILD-PROVENANCE.txt.

Server logs go to the journal: journalctl -u iggy-server.

To change a setting, edit /etc/iggy/server.env and run sudo systemctl restart iggy-server. The start check refuses settings that would switch off TLS on 8090 or turn on QUIC, the WebSocket transport or CORS.

14

Backup and maintenance

  • Back up /var/lib/iggy with the server stopped (sudo systemctl stop iggy-server), or take EBS snapshots of the root volume. Also keep /etc/iggy, /etc/sac and the credentials file.
  • Set retention per topic. Give topics a message expiry or a maximum size, as in Step 6, so the log does not fill the root volume. To grow the volume later, make an EBS volume modification and run sudo growpart /dev/nvme0n1 1 && sudo resize2fs /dev/nvme0n1p1.
  • Size the instance for throughput. Iggy runs one worker per vCPU. An m5.large is a good start, and more vCPUs and memory give higher throughput.
  • Upgrade by relaunching. Ubuntu and Docker security updates install automatically through unattended-upgrades. Iggy itself is the pinned image, so to upgrade, launch the newer version from the listing and move your data across.
15

Security recommendations

  • Limit TCP 8090 in the security group to the networks your applications run in, and limit TCP 443 and 22 to your admin network. The console and the root user can delete streams and messages, so treat them as administrative access.
  • Give each application its own user with only the permissions it needs (Step 9). Rotate the root password if you have shared the credentials file (Step 10).
  • Serve Iggy under your own DNS name with a certificate from a trusted CA (Step 11), and keep TLS verification on in every client.

Support and licensing

SecureAnyCloud provides 24/7 technical support for this image by email and live chat at sales@secureanycloud.com.

Apache Iggy is licensed under the Apache License 2.0. Its LICENSE, NOTICE and the licenses of the components in the image are on the instance in /usr/share/doc/apache-iggy. Docker Engine is open source software under the Apache License 2.0, and nginx is under the BSD 2-Clause license.

Apache Iggy, Iggy, Apache and the Apache feather logo are trademarks of the Apache Software Foundation. SecureAnyCloud is not affiliated with or endorsed by the Apache Software Foundation.

Deploy on AWS

Launch Apache Iggy 0.9.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS