big-AGI 2.0.5 on AWS User Guide
Overview
This guide covers launching and verifying big-AGI 2.0.5 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Ollama, LiteLLM & Self Hosted LLM Server AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other LLM servers, gateways and chat interfaces in the listing are described on the listing's guide hub.
big-AGI is an open-source (MIT) AI chat and agent workspace. It gives you one web interface for the models you already use: OpenAI, Anthropic, Google Gemini, OpenRouter and more, or a self-hosted server such as Ollama, LocalAI or llama.cpp. It adds personas, side-by-side "Beam" answers from several models, voice calls, file and link attachments, diagram and code rendering, and chat import and export.
big-AGI is a chat client, not a model host: the models run at your providers or on your own inference server, so it needs no GPU. big-AGI has no server-side accounts and stores nothing on the server. Your provider keys, chats and personas live in your browser's local storage.
What is on the image:
- big-AGI 2.0.5, the upstream container image
ghcr.io/enricoros/big-agi:v2.0.5, pinned by digestsha256:539f8da1692222e35356e9499446405e99ad6c794ae264f5c989c038dbecfbc2, run by Docker CE (official Docker apt repository) with a restart policy - big-AGI bound to loopback only (
127.0.0.1:3000), with nginx as the only network-facing service - HTTPS on port 443 with a self-signed certificate created on your instance's first boot; port 80 redirects to HTTPS
- A per-instance login (HTTP Basic authentication, user
admin) generated on first boot into a root-only file big-agi-set-hostname, a helper that serves big-AGI under your own DNS name and re-issues the certificate for it
Default user: ubuntu · Architecture: x86_64 · Root volume: 12 GiB gp3
Security posture
- No default credential. The login password is generated on each instance's first boot and stored only as a bcrypt hash (
/etc/nginx/big-agi.htpasswd) and in a root-only file. It is never in the image, so no two instances share one. - Every page and API route needs the login. big-AGI itself has no authentication, so nginx enforces it in front of everything: the UI and every
/apiroute, including the routes that call model providers. The only open path is nginx's own/healthzliteral, which never reaches big-AGI. - Fails closed. nginx will not start until first boot has finished and the per-instance password file exists.
- Only nginx faces the network. It answers only for the configured hostname (any other
Hostheader gets its connection closed), uses TLS 1.2 or 1.3, refuses cross-originPOSTrequests and WebSocket upgrades (403), strips CORS headers, and replaces any client-suppliedX-Forwarded-For,X-Real-IPorForwardedheader with the real peer address. - The hostname comes from configuration, never from a request. First boot reads it from EC2 instance metadata (IMDSv2), or from
/etc/cloudimg/big-agi-hostnameif you set one. - No instance metadata access for the app. big-AGI's server fetches whatever provider address a signed-in user enters, so the container is blocked from the instance metadata service (169.254.169.254 and fd00:ec2::254) by a Docker
DOCKER-USERfirewall rule, which is in place before the container starts. nginx is blocked from it by systemdIPAddressDeny. A provider address can never read the instance's role credentials. - IMDSv2 required. The AMI is registered with
ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests. - No third-party analytics. The upstream container image has its maintainer's Google Analytics ID built into the web app, so every browser session would report to a third party. This image removes that ID from the JavaScript nginx serves, so big-AGI never loads Google Analytics. Next.js telemetry is also off.
- Per-instance identity. SSH host keys, the machine ID and Docker's engine ID are generated fresh on every instance.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- An API key for at least one model provider (OpenAI, Anthropic, Gemini, OpenRouter and so on), or a self-hosted model server such as Ollama that the instance can reach
Recommended instance type: m5.large. big-AGI is a web client and holds no model, so 2 vCPUs and 8 GiB of memory are ample; the model work happens at your providers.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| big-AGI 2.0.5 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened Ollama, LiteLLM & Self Hosted LLM Server AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose big-AGI 2.0.5 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Security group: the listing's default group opens SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address, and restrict 80 and 443 to the networks your users browse from.
- Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.
Option B: AWS CLI
Run this on your own workstation. It creates a security group that allows SSH from your management address and HTTPS from your users' network, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR ranges with your own:
SG=$(aws ec2 create-security-group --group-name big-agi \
--description "big-AGI chat" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr 203.0.113.0/24
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr 203.0.113.0/24
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=big-agi}]'
You do not need --metadata-options: the AMI already makes IMDSv2 required.
Step 2: Connect and find your login
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot finishes within a minute of launch. It sets the hostname big-AGI is served as (the instance's public DNS name when it has one), creates the TLS certificate for it, starts the big-AGI container on loopback, generates the login password, and writes /root/big-agi-credentials.txt (mode 0600, root). Then it starts nginx. These are the entries in that file (the password is not shown here):
sudo grep -E '^BIG_AGI_(URL|USERNAME)=' /root/big-agi-credentials.txt
sudo grep -oE '^[A-Z_]+=' /root/big-agi-credentials.txt
sudo stat -c '%a %U:%G' /root/big-agi-credentials.txt
BIG_AGI_URL=https://ec2-3-86-100-237.compute-1.amazonaws.com/
BIG_AGI_USERNAME=admin
BIG_AGI_URL=
BIG_AGI_USERNAME=
BIG_AGI_PASSWORD=
600 root:root
| Value | What it is for |
|---|---|
BIG_AGI_URL |
The address of this instance's big-AGI |
BIG_AGI_USERNAME |
Login username (always admin) |
BIG_AGI_PASSWORD |
Login password, generated on this instance |
To read the password, run sudo cat /root/big-agi-credentials.txt in your own session and store it in your password manager.
Step 3: Confirm big-AGI is running
The first-boot unit, Docker, the metadata firewall rule and nginx are active. Only sshd and nginx listen beyond loopback; big-AGI itself is on 127.0.0.1:3000, so the nginx login is the only way in.
systemctl is-active big-agi-firstboot.service docker.service cloudimg-docker-imds-deny.service nginx.service
sudo docker ps --format '{{.Names}} {{.Ports}}'
sudo ss -Htlnp | awk '{print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u
curl -sk https://127.0.0.1/healthz
active
active
active
active
big-agi 127.0.0.1:3000->3000/tcp
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"))
127.0.0.1:3000 users:(("docker-proxy"))
127.0.0.53%lo:53 users:(("systemd-resolve"))
127.0.0.54:53 users:(("systemd-resolve"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
ok
/healthz is answered by nginx itself and reveals nothing, so it is safe to use as a load balancer health check.
Step 4: Prove the login and the web defaults
Anonymous requests to the UI and to the API are refused, and so is a password that is wrong by one character. This instance's own password is accepted. A cross-origin POST is refused even with the right password, an unknown Host header gets no answer, and plain HTTP redirects to the configured HTTPS name:
H=$(sudo sed -n 's/^set \$app_host "\(.*\)";$/\1/p' /etc/nginx/big-agi-host.conf)
P=$(sudo grep '^BIG_AGI_PASSWORD=' /root/big-agi-credentials.txt | cut -d= -f2-)
curl -sk -o /dev/null -w 'no password -> %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'no password, API -> %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/edge/llmOllama.listModels"
curl -sk -o /dev/null -w 'wrong password -> %{http_code}\n' -K <(printf 'user = "admin:%sX"\n' "$P") --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'this instance password -> %{http_code}\n' -K <(printf 'user = "admin:%s"\n' "$P") --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'cross-origin POST -> %{http_code}\n' -K <(printf 'user = "admin:%s"\n' "$P") --resolve "$H:443:127.0.0.1" \
-X POST -H 'Origin: https://evil.example' -H 'Content-Type: application/json' -d '{}' "https://$H/api/edge/x"
curl -sk -o /dev/null -w 'unknown Host -> %{http_code}\n' -H 'Host: evil.example' https://127.0.0.1/ || true
curl -s -o /dev/null -w 'http -> %{http_code} %{redirect_url}\n' -H "Host: $H" http://127.0.0.1/
no password -> 401
no password, API -> 401
wrong password -> 401
this instance password -> 200
cross-origin POST -> 403
unknown Host -> 000
http -> 301 https://ec2-3-86-100-237.compute-1.amazonaws.com/
The password is passed to curl through a config file on a file descriptor, so it never appears on a command line or in your shell history.
Step 5: Sign in to big-AGI
Browse to BIG_AGI_URL (https://<public-dns-name>/). The certificate is self-signed, so your browser warns once; accept it for this host, or install your own certificate (Step 8). The browser then asks for a username and password: use admin and BIG_AGI_PASSWORD.
On your first visit big-AGI shows a short notice that you bring your own API credentials. Choose I understand and it opens Setup AI Models.
Step 6: Connect your model providers
big-AGI has no provider configured, and no key ships with the image. In Setup AI Models, paste the API key for any of the popular providers (OpenAI, Anthropic, Gemini, OpenRouter), or choose More Services for the full list. Everything you enter is saved in your browser's local storage, not on the instance:

To use a self-hosted model server, switch to Local and enter the server's address, for example the private address of an instance running the Ollama version of this listing (http://<private-ip>:11434). The address must be reachable from the instance, because big-AGI's server makes the call. Inside the big-AGI container, 127.0.0.1 is the container itself, not the instance. Make sure the model server's security group allows port 11434 from this instance.

Step 7: Start chatting
Choose skip (or Done once a key is entered) to reach the chat home. Pick a persona, type in the composer and press Chat, or Beam to ask several models at once and merge their answers:

The Apps & Tools button in the left rail opens Create Personas, Compare Text, News, the AI Inspector and Clipboard History:

Your chats are kept in the browser you use. Use Export in the chat drawer to back them up, and Import to move them to another browser.
Step 8: Serve big-AGI under your own DNS name
Point a DNS record at the instance (an Elastic IP keeps the address stable), then tell big-AGI the name. The helper re-issues the self-signed certificate for it, updates nginx's accepted names and the cross-origin guard, records the name in /etc/cloudimg/big-agi-hostname (kept across reboots) and updates BIG_AGI_URL:
sudo big-agi-set-hostname chat.example.com
big-agi-set-hostname: big-AGI is served at https://chat.example.com/
To use a certificate from your own CA or from Let's Encrypt instead, replace /etc/ssl/big-agi/big-agi.crt and /etc/ssl/big-agi/big-agi.key (keep the key mode 0600, owner root) and run sudo systemctl reload nginx.
Step 9: Check the security controls
The certificate was made for this instance on first boot, the container's traffic to the instance metadata service is dropped, and nginx is denied it too. The last line runs a request to the metadata service from inside the big-AGI container:
H=$(sudo sed -n 's/^set \$app_host "\(.*\)";$/\1/p' /etc/nginx/big-agi-host.conf)
openssl s_client -connect 127.0.0.1:443 -servername "$H" </dev/null 2>/dev/null | openssl x509 -noout -subject -enddate
sudo iptables -S DOCKER-USER | grep DROP
systemctl show -p IPAddressDeny --value nginx.service
sudo docker exec big-agi node -e 'fetch("http://169.254.169.254/latest/api/token",{method:"PUT",headers:{"X-aws-ec2-metadata-token-ttl-seconds":"60"},signal:AbortSignal.timeout(5000)}).then(r=>console.log("reached: HTTP "+r.status)).catch(e=>console.log("blocked: "+((e.cause&&e.cause.code)||e.name)))'
subject=CN = ec2-3-86-100-237.compute-1.amazonaws.com, O = cloudimg
notAfter=Jan 21 23:04:10 2029 GMT
-A DOCKER-USER -d 169.254.169.254/32 -j DROP
-A DOCKER-USER -d 168.63.129.16/32 -j DROP
169.254.169.254/32 168.63.129.16/32
blocked: TimeoutError
The running container is the pinned release, telemetry is off, and the Google Analytics ID built into the upstream image is not served to browsers (the count is 0):
sudo docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "$(sudo docker inspect --format '{{.Image}}' big-agi)"
sudo docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' big-agi | grep TELEMETRY
H=$(sudo sed -n 's/^set \$app_host "\(.*\)";$/\1/p' /etc/nginx/big-agi-host.conf)
P=$(sudo grep '^BIG_AGI_PASSWORD=' /root/big-agi-credentials.txt | cut -d= -f2-)
APP=$(curl -sk -K <(printf 'user = "admin:%s"\n' "$P") --resolve "$H:443:127.0.0.1" "https://$H/" | grep -oE '/_next/static/chunks/pages/_app-[A-Za-z0-9_-]+\.js' | head -1)
curl -sk --compressed -K <(printf 'user = "admin:%s"\n' "$P") --resolve "$H:443:127.0.0.1" "https://$H$APP" | grep -c 'G-D4J355ELL1' || true
ghcr.io/enricoros/big-agi@sha256:539f8da1692222e35356e9499446405e99ad6c794ae264f5c989c038dbecfbc2
NEXT_TELEMETRY_DISABLED=1
0
Step 10: Change the login password
The password is read from standard input, so it never appears on a command line, in your shell history or in the system logs. Enter the new password when prompted, then reload nginx:
sudo htpasswd -i -B /etc/nginx/big-agi.htpasswd admin
sudo systemctl reload nginx
Then put the new value in /root/big-agi-credentials.txt (or delete that file) so it does not hold a stale password.
Maintenance
- Logs:
sudo docker logs big-agifor big-AGI,/var/log/nginx/for HTTPS access,sudo journalctl -u big-agi-firstboot.servicefor first boot. - Restart:
sudo docker restart big-agi, orsudo systemctl restart nginx. - Operating system updates: Ubuntu's unattended security upgrades stay on, so nginx, Docker and the rest of the OS keep receiving patches. Apply the rest with
sudo apt update && sudo apt upgradein a maintenance window. - Upgrading big-AGI: the container image is pinned by digest and first boot never pulls. To upgrade deliberately, pull a newer release by digest, remove the
big-agicontainer, and run the new image with the same options the first-boot script uses (/usr/local/sbin/big-agi-firstboot.sh):--restart unless-stopped,-e NEXT_TELEMETRY_DISABLED=1and-p 127.0.0.1:3000:3000. Never publish the port on all interfaces, and never use host networking: that would bypass both the login and the metadata firewall rule. A newer release may build in a different analytics ID; check it with the last command of Step 9. - Your data: nothing is stored on the server. Chats, personas and provider keys are in your browser; export them from the chat drawer to back them up.
Troubleshooting
The browser keeps asking for a password. Use admin and BIG_AGI_PASSWORD from /root/big-agi-credentials.txt exactly, over https://.
nginx will not start. It waits for first boot to finish and for the password file to exist, by design. Check first boot with sudo journalctl -u big-agi-firstboot.service --no-pager | tail -20.
A model server is "not reachable". The address is called from the instance, not from your browser. Use an address the instance can reach (a private IP in the same VPC, or a public endpoint), and allow the port in that server's security group. Instance metadata addresses are blocked on purpose.
Support
cloudimg provides 24/7 support for this image by email and live chat: launching and connecting, provider setup, TLS and hostnames, and upgrades.
big-AGI is developed by Enrico Ros and contributors and is released under the MIT licence. Ubuntu is a registered trademark of Canonical Ltd. Docker is a trademark of Docker, Inc. All other product and company names are trademarks or registered trademarks of their respective holders. cloudimg is not affiliated with or endorsed by them.