Analytics cookies only (Google Analytics, PostHog). No ad networks, no cross-site tracking. See our privacy policy.
Updated Oct 2026
verified on Ubuntu 26.04 · Oct 2026Self-host Appsmith Community Edition on an 8 GB VPS with the all-in-one image pinned to a release, a loopback port behind Caddy HTTPS and appsmithctl backups.
Appsmith is a low-code builder for internal tools: you
drag widgets onto a canvas, bind them to REST and GraphQL APIs or to databases,
add JavaScript where a binding needs logic, and publish the result as an app
your team logs into. The Community Edition repository is Apache-2.0, and
the official appsmith-ce image is an all-in-one container that runs MongoDB,
Redis and PostgreSQL inside it. That is what makes it the simplest of the
low-code internal-tool builders
to run: one service, one volume. It is rated 2 / 5 to deploy.
The trade-off is memory. One container holding three databases plus a Java backend wants a bigger server than the others in its category. If you would rather start from a data model than a screen, see NocoBase and its deploy guide; if you want workflows or an internal database built in, see ToolJet and Budibase.
Appsmith's Docker page says to plan for at least 8 GB of RAM on the host for the all-in-one deployment, and notes that moving MongoDB, Redis and PostgreSQL to external services may reduce the container's memory needs. It gives no CPU figure. That 8 GB is the floor this guide uses.
In our install check (GCP e2-standard-2, Ubuntu 26.04, October 2026) the stack idled at about 1.1 GB of RAM and about 4.1 GB of Docker disk with no apps built yet. Idle is not load: the Java backend and the embedded MongoDB both grow as people use the editor, which is why the upstream figure is so much higher than the idle measurement. Take the 8 GB plan.
Install Docker and the Compose plugin by following Docker and Compose on Ubuntu, and confirm the versions:
docker --version
docker compose version
Open SSH plus the two ports Caddy will use. Appsmith itself gets no public port:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
Docker publishes container ports by editing the firewall directly, which
bypasses ufw rules. That is why the compose file below binds Appsmith's port
to 127.0.0.1 instead of relying on ufw to block it.
Write the compose file. Everything in it except the port binding is the file
we verified against a fresh server: a pinned Community Edition tag, the
./stacks volume, and the image's own healthcheck with its start period
stretched to the five minutes upstream quotes for a first boot.
mkdir -p ~/appsmith && cd ~/appsmith
cat > docker-compose.yml <<'EOF'
services:
appsmith:
image: appsmith/appsmith-ce:v2.5.0
container_name: appsmith
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
volumes:
- ./stacks:/appsmith-stacks
healthcheck:
test: /opt/appsmith/healthcheck.sh
interval: 15s
timeout: 15s
start_period: 300s
EOF
Two things to notice. The image is appsmith-ce; the compose example on
upstream's Docker page defaults to appsmith-ee, the Commercial edition, so
check which one you are pulling. And the host side of the port mapping is
127.0.0.1:8080, so only processes on the server (Caddy, below) can reach it.
Start it and wait for the editor to answer:
cd ~/appsmith
docker compose up -d
out=000
for i in $(seq 1 60); do
out=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/)
[ "$out" = "200" ] && break
sleep 10
done
docker compose ps
echo "editor over loopback: $out"
[ "$out" = "200" ] || false
Upstream says the first start can take up to 5 minutes while the embedded
databases initialise, so the loop allows ten. The check is one of those the
image's healthcheck.sh makes: it requests http://localhost/ inside the
container and requires a 200 (it also checks the supervised processes and
the backend's /api/v1/health), which is why / is the right
readiness endpoint here.
Create an A record for your hostname (say tools.example.com) pointing at
the server. With no domain yet, an sslip.io name resolves to your server's
IP with no DNS setup, which is what this guide uses so every command runs as
written:
DOMAIN="appsmith.$(curl -4s https://ifconfig.me).sslip.io"
echo "$DOMAIN"
Keep this shell session open (or re-run the line) for the rest of the guide, and replace the value with your real hostname when you have one.
Appsmith ships its own Caddy inside the container and can issue a certificate
itself when you set a custom domain in Admin Settings (upstream: "When
Appsmith restarts, it will generate an SSL certificate for your custom
domain", with "Ports 80 and 443 must be open and accessible" as the
prerequisite). This guide does not use that path. It keeps the container
on a loopback port, exactly as the other guides on this site do, and puts a
separate Caddy in front; the image keeps serving plain HTTP on port 80
regardless, which is what its own healthcheck probes. So leave the Custom
Domain field and APPSMITH_CUSTOM_DOMAIN unset.
Run Caddy as a container on the host network. It obtains and renews a Let's Encrypt certificate on its own (see Automatic HTTPS with Caddy) and proxies to the loopback port:
mkdir -p ~/caddy
cat > ~/caddy/Caddyfile <<EOF
$DOMAIN {
reverse_proxy 127.0.0.1:8080
}
EOF
docker rm -f caddy >/dev/null 2>&1 || true
docker run -d --name caddy --restart unless-stopped --network host \
-v ~/caddy/Caddyfile:/etc/caddy/Caddyfile:ro -v caddy_data:/data caddy:2
Check that HTTPS answers with a trusted certificate, retrying while the certificate is issued:
out=000
for i in $(seq 1 24); do
out=$(curl -s -o /dev/null -w '%{http_code} %{ssl_verify_result}' "https://$DOMAIN/") && [ "${out%% *}" = "200" ] && break
sleep 5
done
echo "editor over HTTPS and certificate check: $out"
[ "${out%% *}" = "200" ] || false
A second number of 0 means the certificate verified.
Open https://tools.example.com (your hostname). The first visit shows the
signup form; the account you create there is the first user of the instance.
Use your own email address.
Then harden the instance through docker.env. Appsmith's environment
variables for Docker installs live in stacks/configuration/docker.env
inside the installation folder; the container generates that file on its
first start with random passwords and secrets, which is why this step comes
after the start rather than before it. Three variables are worth setting now,
all from upstream's environment-variable reference:
APPSMITH_DISABLE_TELEMETRY=true. Telemetry is enabled by default;
this turns off the anonymous usage data collection.APPSMITH_SIGNUP_DISABLED=true. Stops new account creation. Upstream
notes the signup page still shows but errors when someone tries, that
existing logins are unaffected, and that it does not block the
administrators named in the next variable.APPSMITH_ADMIN_EMAILS=you@example.com. The comma-separated list of
Instance Administrators, who get full control over all instance settings.cd ~/appsmith
ENV_FILE=stacks/configuration/docker.env
for kv in APPSMITH_DISABLE_TELEMETRY=true APPSMITH_SIGNUP_DISABLED=true APPSMITH_ADMIN_EMAILS=you@example.com; do
k=${kv%%=*}
if sudo grep -q "^$k=" "$ENV_FILE"; then
sudo sed -i "s|^$k=.*|$kv|" "$ENV_FILE"
else
echo "$kv" | sudo tee -a "$ENV_FILE" >/dev/null
fi
done
sudo grep -n '^APPSMITH_DISABLE_TELEMETRY\|^APPSMITH_SIGNUP_DISABLED\|^APPSMITH_ADMIN_EMAILS' "$ENV_FILE"
Replace you@example.com with the address you signed up with. Upstream says
these variables take precedence over the Admin Settings UI, and that after
editing docker.env you restart the Appsmith container. The restart is
another slow boot, so wait for it the same way:
cd ~/appsmith
docker compose restart appsmith
out=000
for i in $(seq 1 60); do
out=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/)
[ "$out" = "200" ] && break
sleep 10
done
echo "editor after restart: $out"
[ "$out" = "200" ] || false
Sign back in, create a workspace, add a datasource and build a first app. Invitations and password-reset emails need outgoing mail; upstream's Admin Settings cover SMTP, and nothing in this guide configures it.
Everything Appsmith persists lives in ./stacks: the embedded databases,
docker.env (which holds the encryption password and salt the stored
datasource credentials depend on) and Git data. Upstream's appsmithctl backup command archives exactly that set: "the internal database,
docker.env, and Git data". Run interactively it prompts for a password and
encrypts the archive, and upstream warns that without that password the
backup cannot be restored. With --non-interactive (or with no terminal
attached) it skips the prompt and writes an unencrypted archive, which is the
form that fits a cron job:
docker exec appsmith appsmithctl backup --non-interactive
sudo ls -lh ~/appsmith/stacks/data/backup/
Inside the container the archive lands in /appsmith-stacks/data/backup/,
which is ./stacks/data/backup/ on the host, named
appsmith-backup-DATE_AND_TIMESTAMP.tar.gz. Treat an unencrypted archive as
sensitive: it contains every datasource secret you have saved. Copy it off the
server to object storage or another machine on a schedule, and keep
docker-compose.yml with it.
To restore, upstream's appsmithctl restore expects the archive back in
/appsmith-stacks/data/backup/ and, from v2.4.1, runs without prompts when
told which file to use:
docker exec appsmith appsmithctl restore --backup-file appsmith-backup-DATE_AND_TIMESTAMP.tar.gz --non-interactive --force
An untested backup is a guess, so restore into a throwaway server now and then.
Upstream's prerequisites for a Compose upgrade are a backup (above), at least
2 GB of free disk, and MongoDB 5.0 or later if you run an external one. The
procedure is to set the Appsmith service's image to the new pinned tag, pull,
remove the old container and start again. Very old installs must step through
checkpoint releases first (v1.9.2, then the v1.96 to v1.99 upgrade before
v2.0), so read upstream's update page before jumping several versions.
cd ~/appsmith
sed -i 's|appsmith/appsmith-ce:v2.5.0|appsmith/appsmith-ce:NEW_VERSION|' docker-compose.yml
docker compose pull
docker compose rm -fsv appsmith
docker compose up -d
Upstream writes the last line with the older docker-compose binary; the
Compose plugin syntax above does the same thing. Expect the same up-to-five-
minute boot afterwards.
curl http://127.0.0.1:8080/ returns 000 or 502 for minutes. The
container is still starting; upstream allows five minutes. Watch
docker compose logs -f appsmith and docker compose ps, which shows
(health: starting) until the healthcheck passes. If it never passes, check
free -m: three embedded databases plus the Java backend on a server smaller
than 8 GB is the first suspect.
You pulled the wrong edition. If docker compose ps shows an
appsmith-ee image, the compose file was copied from upstream's example
rather than this guide. Change the image to appsmith/appsmith-ce:<tag> and
run docker compose up -d.
Signup is disabled but you have no account. APPSMITH_SIGNUP_DISABLED
was set before the first user existed. Upstream says the setting does not
block administrators named in APPSMITH_ADMIN_EMAILS, so put your address
there and restart; failing that, set the variable back to false, restart,
sign up, then re-enable it.
Changes to docker.env have no effect. The file is read at container
start. Restart the Appsmith container after every edit, and remember that
these variables override whatever the Admin Settings UI shows.
Port 8080 is reachable from the internet. The port line lost its
127.0.0.1: prefix. Fix the compose file, docker compose up -d, then check
that ss -tln | grep 8080 shows 127.0.0.1.
Certificate never issues. That is a Caddy and DNS problem rather than an
Appsmith one: confirm the A record resolves (dig +short tools.example.com)
and that ports 80 and 443 are reachable. Look at docker logs caddy.
You are done when https://tools.example.com loads with a valid certificate,
you can sign in as the first user, signup is refused to everyone else, port
8080 is bound only to 127.0.0.1, and an appsmithctl archive is landing off
the server.
From here, connect your first datasource, build an app and invite the team once SMTP is configured. If the 8 GB footprint is more than you want to run for a small internal tool, NocoBase runs beside a single PostgreSQL in a fraction of the memory.