REI3 Low Code Platform 3.13.2 on AWS User Guide
Overview
This guide covers launching and using the REI3 Low Code Platform 3.13.2 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
REI3 is an open-source (MIT) low-code platform for building business applications in the browser: data models, forms, lists, calendars, Gantt charts, dashboards, roles and access rules, with ready-made applications (time tracking, IT asset management, CRM and more) installable from REI3's application repository. This image runs REI3 on your own AWS account, with its database on the same instance.
What cloudimg adds on top of REI3:
- HTTPS from the first boot. nginx serves REI3 on port 443 with a certificate generated on your instance's first boot. Port 80 only redirects to HTTPS, and a request for any name other than your instance's own gets no answer.
- No default login. REI3 creates an
adminaccount with the passwordadmin. On this image that password never works: first boot replaces it with a random per-instance password before REI3 or nginx starts, and writes it to/var/lib/cloudimg/rei3-credentials.txt(root only). The image contains no password, no certificate, no signing secret and no host name. - Built from source with patched dependencies. REI3 3.13.2 is compiled from its published source with Go 1.27.3, after raising four Go libraries (
golang.org/x/net,golang.org/x/crypto,github.com/andybalholm/brotliandgithub.com/klauspost/compress) to releases that fix known vulnerabilities.govulncheckfinds no reachable vulnerability in the result, and the build is reproducible from the recorded source (Step 12). - REST calls cannot reach metadata or your network. REI3 applications can call web services, so REI3 runs inside an egress fence: it can reach public HTTPS but not the EC2 instance metadata service, link-local, private, shared or loopback addresses. You can open a private range for it when you need one (Step 7).
- Database on a socket. PostgreSQL 16 listens on its local socket only and REI3 signs in to it as its Linux user, so there is no database password. The database is created on your instance's first boot: the image ships an empty one.
- A hardened front door. Sign-in requests are rate limited per client address (for every spelling of the sign-in paths), nginx always replaces
X-Forwarded-For, cross-site requests and cross-site WebSocket connections are refused, and request sizes are bounded (Step 8). - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: REI3 3.13.2 (/opt/rei3/r3, built from the upstream v3.13.2 source), PostgreSQL 16, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The build record, REI3's licences and the source reference are in /usr/share/doc/cloudimg-rei3/.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
- A security group that allows TCP 443 (REI3), TCP 80 (redirects to HTTPS) and TCP 22 (SSH). Open 443 to the networks your users work from; keep 22 open to your own networks only.
- A current web browser.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick REI3 Low Code Platform 3.13.2 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (REI3 and PostgreSQL share the instance; t3.medium is enough for a few users), your key pair, your subnet and the security group above.
With the AWS CLI (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 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=rei3}]'
An Elastic IP keeps the server's address, and so its name and certificate, the same across stops and starts.
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| REI3 Low Code Platform 3.13.2 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot takes under a minute: it sets the server's name, generates the certificate, creates the PostgreSQL database and REI3's schema, replaces the admin password and mints REI3's signing secret, and only then starts REI3 and nginx. Check that everything is running:
systemctl is-active rei3-firstboot rei3-egress-fence postgresql@16-main rei3 nginx \
&& sudo test -f /var/lib/cloudimg/rei3-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network. REI3 listens on the loopback address 127.0.0.1:8090 behind nginx, and PostgreSQL has no network port at all:
sudo ss -lntp | awk 'NR>1 && $4 !~ /^127\.0\.0\.5[34]/ {print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u | tee /dev/stderr \
| awk '$1 !~ /^127\./ {sub(/.*:/, "", $1); print $1}' | sort -un | xargs | grep -qx '22 80 443' && echo "network listeners: 22 80 443"
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:8090 users:(("r3"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
network listeners: 22 80 443
Step 4: Get your address and sign-in
Run sudo cat /var/lib/cloudimg/rei3-credentials.txt to see your address, user name and password. The file is readable by root only and holds these settings, all generated for this instance:
sudo sed -n 's/^\(REI3_[A-Z0-9_]*\)=.*/\1/p' /var/lib/cloudimg/rei3-credentials.txt | tee /dev/stderr \
| grep -qx REI3_ADMIN_PASSWORD && echo "credentials present"
REI3_URL
REI3_HOST
REI3_ADMIN_USER
REI3_ADMIN_PASSWORD
REI3_TLS_CERT
REI3_TLS_SHA256_FINGERPRINT
credentials present
REI3_URL is https://<your instance's public DNS name>/, the address you open in a browser, REI3_ADMIN_USER is admin and REI3_ADMIN_PASSWORD is its password. The settings that are not secret can be shown on their own:
sudo grep -E '^REI3_(URL|ADMIN_USER|TLS_SHA256_FINGERPRINT)=' /var/lib/cloudimg/rei3-credentials.txt \
| tee /dev/stderr | grep -qx 'REI3_ADMIN_USER=admin'
REI3_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/
REI3_ADMIN_USER=admin
REI3_TLS_SHA256_FINGERPRINT=45:65:D9:...:92:07
The default password admin is refused, and so is a wrong one:
H="$(sudo sed -n 's/^REI3_HOST=//p' /var/lib/cloudimg/rei3-credentials.txt)"
for pw in admin not-the-password; do
printf '{"username":"admin","password":"%s"}' "$pw" \
| curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/rei3/rei3.crt --resolve "$H:443:127.0.0.1" \
-H 'Content-Type: application/json' --data-binary @- "https://$H/api/auth"
done | xargs | tee /dev/stderr | grep -qx '401 401' && echo "default and wrong passwords refused"
401 401
default and wrong passwords refused
Step 5: Sign in
The certificate is self-signed and generated on your instance, so your browser warns about it the first time. Compare the fingerprint the browser shows with REI3_TLS_SHA256_FINGERPRINT and accept it, or use your own domain and a trusted certificate (Step 10). While the certificate is self-signed, browsers do not install REI3's service worker (which lets REI3 run as an installable web app); everything else works, and a trusted certificate enables it.
Open REI3_URL in your browser and sign in as admin with REI3_ADMIN_PASSWORD:

After signing in you land on REI3's home page. On a new instance it offers to install the Core Company set of applications (absence management, time tracking, projects and tasks, request handling, a password safe and IT asset management), an application from REI3's repository, or one from a file:

REI3 starts in maintenance mode, as every new REI3 installation does: only administrators can sign in, and the sign-in page says so. Install or build your applications first, then switch maintenance mode off under Admin > System so that your users can sign in.
Change the admin password in the browser under the user menu, or with sudo rei3-rotate-password (Step 9). Create an account for each person under Admin > Users rather than sharing admin.
Step 6: Install applications and build your own
The Admin area (the Maintenance button in the header) manages the instance: applications, users and memberships, email accounts, backups, files, logs and the scheduler.

- Install a ready-made application: Admin > Applications > Install from repository lists the applications in REI3's public repository. Choose one and install it; REI3 downloads it over HTTPS (the egress fence allows public HTTPS).
- Build your own: switch on Builder mode under Admin > System, then open the Builder from the header to define relations (tables), forms, menus and roles. REI3's documentation covers the Builder in detail.
Admin > System also holds the public host name (set by the image, see Step 10), an outbound proxy, the password rules and maintenance mode:

Step 7: Outbound calls and the egress fence
REI3 applications can call web services: a backend function runs instance.rest_call(...) and REI3 sends the request from the server. On a cloud instance that is a server-side request forgery risk, because the server can reach addresses your users cannot, such as the instance metadata service and your VPC. The image therefore fences the rei3 account twice:
- nftables (
rei3-egress-fence.service) rejects any connection therei3user opens to metadata, link-local, private (RFC 1918), shared, loopback, multicast or reserved addresses, IPv4 and IPv6. Only replies to connections nginx opened to REI3 pass. - systemd (
IPAddressDeny=onrei3.service) drops the same ranges, except loopback, for anything running in REI3's service, whatever user it runs as.
rei3.service refuses to start unless the nftables table is loaded. nginx and PostgreSQL cannot reach the metadata service either. Check the fence:
sudo rei3-egress-fence check && echo "egress fence loaded"
systemctl show -p IPAddressDeny --value rei3.service | tr ' ' '\n' | grep -cE '^(10\.0\.0\.0/8|172\.16\.0\.0/12|192\.168\.0\.0/16|169\.254\.0\.0/16)$' \
| tee /dev/stderr | grep -qx 4 && echo "private and link-local ranges denied to rei3.service"
egress fence loaded
4
private and link-local ranges denied to rei3.service
The rei3 user is refused at once by the metadata service and by this instance's own private address, while public HTTPS works:
P="$(hostname -I | awk '{print $1}')"
for u in http://169.254.169.254/latest/meta-data/ "https://$P/" https://www.cloudimg.co.uk/; do
printf '%s %s\n' "$(sudo -u rei3 curl -sk -m 8 -o /dev/null -w '%{http_code}' "$u" || true)" "${u/$P/PRIVATE_IP}"
done | tee /dev/stderr | awk '{print $1}' | xargs | grep -qx '000 000 200' && echo "fenced: metadata and private refused, public HTTPS allowed"
000 http://169.254.169.254/latest/meta-data/
000 https://PRIVATE_IP/
200 https://www.cloudimg.co.uk/
fenced: metadata and private refused, public HTTPS allowed
Inside REI3 itself a call to a fenced address fails: REI3 logs failed to execute REST call under Admin > Logs (context API), retries it up to five times and then gives up. Because the systemd layer drops the packets rather than answering, such a call waits for REI3's 30-second request timeout before it fails. Calls to public HTTPS services work as normal.
To let REI3 reach a service in your own network, open just that range (metadata, link-local and loopback ranges are always refused). The helper updates both layers and restarts REI3:
sudo rei3-egress-allow add 10.20.30.40/32
sudo rei3-egress-allow list
sudo rei3-egress-allow remove 10.20.30.40/32
REI3 can also use an outbound proxy (Admin > System, Proxy URL); the proxy then decides where REI3's requests may go.
Step 8: What the front door refuses
nginx answers only for the server's own names and passes requests to REI3 on loopback:
H="$(sudo sed -n 's/^REI3_HOST=//p' /var/lib/cloudimg/rei3-credentials.txt)"
{ curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/rei3/rei3.crt --resolve "$H:443:127.0.0.1" "https://$H/"
curl -s -o /dev/null -w '%{http_code}\n' -k -H 'Host: someone-else.example' https://127.0.0.1/
curl -s -o /dev/null -w '%{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/ssl/rei3/rei3.crt --resolve "$H:443:127.0.0.1" -H 'Origin: https://evil.example' "https://$H/api/auth" -X POST
} | xargs | tee /dev/stderr | grep -qx '200 000 301 403' && echo "front door OK"
200 000 301 403
front door OK
- Rate limits. The password routes (
/api/auth,/data/authand the WebSocket sign-in) are limited to 30 a minute per client address (bursts of 20), the rest of REI3's API to 50 requests a second (bursts of 500) and the web client's own files to 200 a second (bursts of 2000, so a browser's first visit loads in full), with at most 256 requests in progress per address; over the limit nginx answers429. The limit applies to every spelling of those paths (//api/auth,/API/AUTH,/%61pi/auth,/api/auth;xand so on), because nginx matches them after normalising the path. REI3's own brute-force protection is off: behind nginx every request comes from127.0.0.1, which REI3 exempts, so the nginx limit is the one that counts. - X-Forwarded-For. nginx replaces any
X-Forwarded-Forheader with the address it accepted the connection from and blanksX-Real-IP,Forwardedand similar headers, so a client cannot change the address the rate limits and logs use. If you put a load balancer in front of the instance, every client then shares the load balancer's address for the rate limits. - Cross-site requests. Requests whose
Originis not the instance's own address get403, and so does a WebSocket upgrade from another site, so a web page elsewhere cannot use a signed-in user's browser against REI3. - Sizes and types. Request bodies are limited to 16 MiB, except file uploads (
/data/upload), which may be up to 512 MiB. Compressed request bodies (Content-Encoding) get415andTRACEgets405. - Headers. Responses carry
Strict-Transport-Security,X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-originand REI3's ownContent-Security-Policy, and nginx does not advertise its version.
What is logged: nginx's access log (/var/log/nginx/access.log) records the client address, request path and status, without query strings. nginx's error log records only start-up problems, not refused requests, so a password in a rejected request never reaches the disk. REI3 logs to its own database table, which you read under Admin > Logs.
Step 9: Run the self-test, change the password
The image ships a self-test that checks the services, the listening ports and the egress fence, that admin/admin and a wrong password are refused over REST and over the WebSocket, and that admin signs in over HTTPS with this instance's certificate:
sudo rei3-selftest | tail -n 1 | tee /dev/stderr | grep -q '^REI3_SELFTEST_OK '
REI3_SELFTEST_OK admin/admin and a wrong password refused (REST + WebSocket), admin logs in over https://ec2-203-0-113-10.compute-1.amazonaws.com/ (logins: admin; applications installed: 0), cross-site requests refused
To give admin a new random password and write it to the credentials file:
sudo rei3-rotate-password
Step 10: Use your own domain
Point a DNS name at the instance (an Elastic IP keeps it stable), then make it the server's address. This generates a new certificate for that name, pins nginx to it and tells REI3 its public address:
sudo rei3-set-site rei3.example.com
To use a certificate from a public CA instead, for example one issued by certbot with a DNS challenge, run rei3-set-site first, then replace /etc/ssl/rei3/rei3.crt (full chain) and /etc/ssl/rei3/rei3.key (mode 0600) and run sudo systemctl reload nginx. REI3's own certificate renewal task is switched off on this image because nginx terminates TLS.
Step 11: Back up and restore
Everything REI3 stores, apart from uploaded files, is in the PostgreSQL database rei3; uploaded files are in /var/lib/rei3/files. REI3's built-in backups (Admin > Backups) write full database dumps to /var/lib/rei3/backups on the schedule you set. To make a dump yourself:
sudo -u postgres pg_dump -Fc rei3 > rei3-$(date +%F).dump
sudo tar -C /var/lib/rei3 -czf rei3-files-$(date +%F).tar.gz files
Copy both off the instance, for example to Amazon S3. Restore them on a new instance of this image with sudo systemctl stop rei3, sudo -u postgres pg_restore --clean --if-exists -d rei3 rei3-<date>.dump, unpacking the files into /var/lib/rei3 (owned by rei3) and sudo systemctl start rei3.
Step 12: Security notes and the build record
The certificate generated at first boot is a server certificate only: it cannot sign other certificates.
openssl x509 -in /etc/ssl/rei3/rei3.crt -noout -ext basicConstraints,keyUsage,extendedKeyUsage | tee /dev/stderr | grep -q 'CA:FALSE'
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
REI3 is the 3.13.2 release built from source, and no database role has a password:
sed -n '1,4p' /usr/share/doc/cloudimg-rei3/BUILD-INFO.txt
sudo -u postgres psql -X -tAc "SELECT 'roles with a password: ' || count(*) FROM pg_authid WHERE rolpassword IS NOT NULL" \
| tee /dev/stderr | grep -qx 'roles with a password: 0'
REI3 3.13.2.7213 (github.com/r3-team/r3 tag v3.13.2, commit 777d3da91935a63efadbadb0feb2103c852d2805), MIT; bundled browser
components MIT / Apache-2.0 / GPL-2.0-or-later (TinyMCE 8.7.0) / BSD-2-Clause / ISC (LICENSE.md)
built from source with Go 1.27.3; binary /opt/rei3/r3 sha256 e1d8c05eec31a93699b651f1dc12513bee1aee2614aed45fb2847b3cbc81a536; SOURCE.txt
dependencies raised to fixed releases: golang.org/x/net@v0.62.0 github.com/andybalholm/brotli@v1.2.7 github.com/klauspost/compress@v1.20.2 golang.org/x/crypto@v0.58.1-0.20261009181718-2f1f834330b2
roles with a password: 0
- Reproducible build.
/usr/share/doc/cloudimg-rei3/SOURCE.txtnames the upstream source archive and its SHA-256, the Go toolchain and the exact build command, which reproduce the binary's SHA-256 recorded inBUILD-INFO.txt. - Thumbnails are off. REI3 makes image thumbnails with ImageMagick when it is installed. This image does not install ImageMagick or Ghostscript (on Ubuntu 24.04 they come from the community-maintained
universearchive), so uploaded files are stored and served normally but REI3 shows no thumbnails for them. - PDFs are made in the browser. REI3's web client includes jsPDF 2.5.1, which it uses to turn a form or an article into a PDF in the signed-in user's browser. jsPDF 2.5.1 has published advisories; the most serious ones concern jsPDF running under Node.js on a server. Nothing on this instance runs jsPDF: it is only sent to browsers as part of REI3's web client, and REI3 updates it upstream.
- Licences. REI3 is MIT licensed. Its web client bundles components under MIT, Apache-2.0, BSD, ISC, MPL-2.0 and GPL-2.0-or-later (TinyMCE) licences, listed in
/usr/share/doc/cloudimg-rei3/LICENSE.md;SOURCE.txtpoints to their corresponding source. - Isolation. REI3 runs as its own unprivileged user
rei3with no login shell and no capabilities, a read-only view of the system except/var/lib/rei3, a 2 GiB memory limit and no access to the credentials file or the TLS key. PostgreSQL 16 and nginx come from Ubuntu'smainarchive.
Step 13: Keep it up to date
Ubuntu security updates, including PostgreSQL and nginx, install automatically. To apply everything now:
sudo apt-get update && sudo apt-get upgrade -y
New REI3 releases arrive as new versions of the AWS Marketplace listing: launch the new version, restore your database and files into it (Step 11) and run sudo rei3-set-site with your domain if you use one. REI3 upgrades its database schema when it starts. Applications from the repository update under Admin > Applications.
Support
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
The other versions on this listing are on the developer and API tools guide.