%%{init: {'theme': 'dark', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"22px", "darkMode":true}}}%%
flowchart LR
subgraph Internet["<strong>Internet</strong>"]
Admin(["Admin<br>SSH client"])
Operator(["Operator<br>Browser"])
Other(["Anyone else"])
end
subgraph SG["<strong>EC2 Security Group</strong>"]
EC2["<strong>splam</strong><br>127.0.0.1:8000"]
end
Admin ==>|"22/tcp<br>admin CIDR/VPN only"| EC2
Operator ==>|"443/tcp<br>HTTPS"| EC2
Other --x|"8000/tcp<br>never opened"| EC2
style Internet fill:#FFFFFF,color:#000000,stroke:#333,stroke-width:1px,rx:10,ry:10
style SG fill:#FFFFFF,color:#000000,stroke:#333,stroke-width:1px,rx:10,ry:10
style Admin fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
style Operator fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
style Other fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
style EC2 fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5,font-family:monospace
AWS
splam controls systemctl/journalctl on the host it runs on, so it has to run directly on the machine it’s managing (not in a container or behind Lambda). On AWS that means an EC2 instance, run as a systemd service with a reverse proxy in front for TLS.
This page assumes no prior AWS experience. If you already know EC2, VPCs, and security groups, skip to step 2.
1. Launch the instance
Where the instance lives
An EC2 instance is a virtual machine that runs inside a VPC (a private network you own within AWS) and one of its subnets (a smaller address range carved out of that VPC, tied to one Availability Zone). Every new AWS account already has a default VPC with a public subnet in each Availability Zone, and that default is fine for a single splam instance. Launch into it unless your organization has its own VPC design you’re expected to follow.
A public subnet is one whose route table sends 0.0.0.0/0 traffic to an internet gateway, which is what gives an instance in it a reachable public IP. splam itself should never be reachable directly (see security groups below), so if your account has a private subnet with a NAT gateway or a VPN/Direct Connect path back to your network, prefer that instead. You’ll SSH in through a bastion host or VPN rather than the public internet.
Choosing an instance type and AMI
Any recent Amazon Linux 2023 or Ubuntu 22.04+ AMI works; splam needs Python 3.11 or newer, which both ship with. A t3.micro or t3.small is plenty. The app itself is lightweight, and the audit trail/incident files are small text and JSON.
Storage
The default 8 GB root EBS volume (gp3) is enough for the OS, the splam install, and normal audit-trail growth. If you’re also using the shell-audit feature (see How-To: Shell Audit Setup), auditd’s own logs in /var/log/audit/ can grow faster under heavy sudo use, and 20 GB gives more headroom before you need to think about log rotation.
Security group
A security group is a stateful firewall attached to the instance (stateful meaning a reply to an allowed outbound or inbound request doesn’t need its own separate rule). Lock it down to two inbound rules:
22/tcp(SSH) from your admin CIDR/VPN only, never0.0.0.0/0443/tcp(HTTPS) from wherever operators connect, ideally also restricted to a known CIDR range rather than the whole internet
Don’t open 8000/tcp (or whatever port splam binds) to the internet. It stays behind the reverse proxy in step 5, reachable only from localhost or from the load balancer’s own security group. Outbound rules can stay at the default “allow all”, since the instance needs outbound access to pull the splam package and any OS updates.
DNS
If operators will reach splam at a real hostname rather than an IP, create an A (or ALIAS) record in Route 53 (or whatever DNS provider you use) pointing at the instance’s Elastic IP, or at the load balancer if you’re using one (step 5). An Elastic IP is a static public IP you allocate and associate with the instance, so it survives a stop/start. Without one, a restarted instance gets a new IP and DNS breaks.
2. Install splam
Same steps as the Tutorial, run as whatever user will own the service. SSH in first:
ssh -i your-key.pem ec2-user@<instance-public-ip>(ec2-user on Amazon Linux, ubuntu on Ubuntu AMIs.)
Install system dependencies. splam itself only needs Python and git, but both AMI families ship Python without venv/pip fully wired up by default on minimal images, so install explicitly:
# Amazon Linux 2023
sudo dnf install -y python3 python3-pip git
# Ubuntu
sudo apt update && sudo apt install -y python3 python3-venv python3-pip gitClone the repo:
sudo git clone https://github.com/mjfrigaard/splam.git /opt/splamChange directories:
cd /opt/splamCreate the Python virtual environment. splam and its dependencies (Shiny for Python and its transitive packages, pulled from PyPI) are installed into this venv rather than the system Python, which is what pip would otherwise refuse to touch under PEP 668:
sudo python3 -m venv .venvInstall dependencies:
sudo .venv/bin/pip install -e .This needs outbound internet access to reach PyPI (pypi.org), so if the instance is in a private subnet, confirm the NAT gateway or VPC endpoint path allows it before this step.
Add admin:
sudo .venv/bin/splam-users add admin3. Grant systemctl privilege
The service account needs rights to control the units it manages. See How-To: Sudo Privileges. Running as root is simplest for a first deploy, and a scoped polkit/sudoers rule is the tighter option once you know which units operators need.
4. Run splam as a systemd unit
systemd is the service manager already running on both Amazon Linux and Ubuntu; a unit file tells it how to start, restart, and supervise a process, which is what keeps splam running across reboots and crashes without a separate process manager.
Bind to 127.0.0.1 (not 0.0.0.0) and let the reverse proxy handle external traffic. Create /etc/systemd/system/splam.service:
[Unit]
Description=splam
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/splam
Environment=SPLAM_DATA_DIR=/var/lib/splam
ExecStart=/opt/splam/.venv/bin/shiny run splam.app:app --host 127.0.0.1 --port 8000
Restart=on-failure
[Install]
WantedBy=multi-user.targetSPLAM_DATA_DIR=/var/lib/splam moves credentials and audit logs out of /opt/splam and into the conventional system-deployment location (see How-To: Manage Logins). Add SPLAM_MAX_FAILED_LOGINS, SPLAM_LOCKOUT_SECONDS, SPLAM_IDLE_TIMEOUT, or SPLAM_AUDIT_LOG as further Environment= lines if the defaults don’t fit.
sudo mkdir -p /var/lib/splamsudo systemctl daemon-reloadsudo systemctl enable --now splamenable makes the unit start on boot; --now also starts it immediately. Confirm it’s actually up before moving on:
sudo systemctl status splam
curl -I http://127.0.0.1:8000The curl should return an HTTP response (even a redirect or login page), confirming splam is listening on 127.0.0.1:8000 as expected. If systemctl status shows failed, check journalctl -u splam -n 50 for the reason before continuing. A common cause at this stage is the venv path in ExecStart not matching where step 2 actually installed it.
5. Put a reverse proxy in front
splam only listens on localhost:8000; something else has to accept the public 443/tcp connection, terminate TLS, and forward the request. Two options:
%%{init: {'theme': 'dark', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"20px", "darkMode":true}}}%%
flowchart TD
Operator(["Operator<br>Browser"]) ==>|"HTTPS :443"| Proxy["Reverse Proxy<br>nginx or ALB"]
Proxy ==>|"HTTP :8000"| Splam["<code>splam</code><br>127.0.0.1:8000"]
Splam -.->|"controls"| Systemd[("systemd<br>+ journald")]
style Operator fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
style Proxy fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
style Splam fill:#4CBB9D,color:#FFFFFF,rx:5,ry:5
style Systemd fill:#FFFFFF,color:#000000,stroke:#333,stroke-width:1px
Option A: nginx on the instance itself. Install it, request a certificate (e.g. via Certbot/Let’s Encrypt, which needs 80/tcp briefly open during issuance), and proxy to splam:
sudo dnf install -y nginx # or: sudo apt install -y nginx/etc/nginx/conf.d/splam.conf:
server {
listen 443 ssl;
server_name splam.example.com;
ssl_certificate /etc/letsencrypt/live/splam.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/splam.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
The Upgrade/Connection headers matter because Shiny for Python holds its UI connection open over a WebSocket, not plain request/response HTTP; without them the proxy will accept the initial page load but the app will never become interactive.
Option B: an Application Load Balancer (ALB). AWS terminates TLS for you using a free certificate from ACM (AWS Certificate Manager), and forwards plain HTTP to the instance. This is less to maintain (no certificate renewal on the box, no nginx config) but costs more than a bare instance and is one more AWS resource to understand:
- Request a certificate for your domain in ACM and validate it (DNS validation via Route 53 is the easiest path).
- Create a target group of type “instance”, protocol HTTP, port
8000, and register the EC2 instance in it. Enable WebSocket support is the ALB default, so no extra flag is needed, but confirm the target group’s health check path returns a non-error response fromsplam(the login page at/works). - Create the ALB itself in the public subnet(s), listener on
443using the ACM certificate, forwarding to that target group. - Point DNS at the ALB’s own DNS name (a
CNAME/ALIAS), not at the instance.
With an ALB, the instance’s security group should only accept 8000/tcp from the ALB’s security group (reference it by security-group ID, not by CIDR), and 443/tcp is opened on the ALB’s security group instead of the instance’s.