Most teams adopt an internal app platform only after the same pattern repeats: business operations keep asking engineering for small forms, approval tools, and dashboards, and those requests compete with product roadmap work. Over time, low-code platforms become attractive because they let teams ship internal workflows quickly while still enforcing data access controls and deployment standards. This guide shows how to deploy Appsmith on Ubuntu using Docker Compose + Traefik + PostgreSQL with production-ready security, TLS, backup, and day-2 operations practices.
You will build a deployment that is practical for real organizations: PostgreSQL data isolation, reverse-proxy hardening, environment-based secret handling, deterministic upgrade flow, health checks, and verification steps your team can run during incident response. The goal is not just "it runs" — the goal is an Appsmith stack your team can maintain confidently over months.
Architecture and flow overview
The stack runs on one Ubuntu host (or VM) with Docker Engine and the Compose plugin. Appsmith runs in a container network with a dedicated PostgreSQL service for persistent metadata and application state. Traefik terminates TLS, handles routing, and exposes only HTTPS endpoints. Secrets are stored in an environment file with restricted permissions and never committed to Git.
- Users: Access Appsmith via HTTPS in browser.
- Traefik: Terminates TLS, routes hostnames, applies redirect from HTTP to HTTPS.
- Appsmith: Internal app runtime and editor.
- PostgreSQL: Durable metadata and configuration storage.
- Backups: Scheduled PostgreSQL dump plus compose/environment snapshots.
Traffic flow: user request → Traefik entrypoint → Appsmith service. Appsmith persists state in PostgreSQL. Backups copy PostgreSQL data and key deployment files to external storage. Monitoring validates service health and certificate status.
Prerequisites
- Ubuntu 22.04/24.04 server with sudo privileges.
- DNS A record for
apps.example.compointed to your server IP. - Open ports: 22, 80, 443.
- At least 4 vCPU, 8 GB RAM, and 80 GB SSD (higher for heavy internal usage).
- A secure location for backups (S3-compatible bucket or remote disk).
sudo apt update && sudo apt -y upgrade
sudo apt -y install ca-certificates curl gnupg ufw jq
# Docker Engine + Compose plugin
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker --version
docker compose version
If the copy button does not work in your browser/editor, manually select the command block and copy it.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status verbose
If the copy button does not work in your browser/editor, manually select the command block and copy it.
Step-by-step deployment
1) Prepare project directories and secrets file
Create a clean deployment root with strict filesystem permissions. Appsmith and PostgreSQL secrets should be generated once and injected through environment variables.
sudo mkdir -p /opt/appsmith/{traefik,database,backups}
sudo chown -R $USER:$USER /opt/appsmith
cd /opt/appsmith
APP_DB_PASSWORD=$(openssl rand -base64 36 | tr -d '\n')
APPSMITH_ADMIN_PASSWORD=$(openssl rand -base64 36 | tr -d '\n')
cat > /opt/appsmith/.env <<'EOF'
DOMAIN=apps.example.com
[email protected]
POSTGRES_DB=appsmith
POSTGRES_USER=appsmith
POSTGRES_PASSWORD=REPLACE_DB_PASSWORD
APPSMITH_ENCRYPTION_PASSWORD=REPLACE_ENCRYPTION_PASSWORD
APPSMITH_SUPERUSER_PASSWORD=REPLACE_SUPERUSER_PASSWORD
TZ=UTC
EOF
sed -i "s|REPLACE_DB_PASSWORD|$APP_DB_PASSWORD|" /opt/appsmith/.env
sed -i "s|REPLACE_ENCRYPTION_PASSWORD|$(openssl rand -base64 48 | tr -d '\n')|" /opt/appsmith/.env
sed -i "s|REPLACE_SUPERUSER_PASSWORD|$APPSMITH_ADMIN_PASSWORD|" /opt/appsmith/.env
chmod 600 /opt/appsmith/.env
If the copy button does not work in your browser/editor, manually select the command block and copy it.
2) Create Docker Compose stack
This compose file pins explicit images, isolates services on an internal network, and maps persistent volumes for PostgreSQL and Appsmith. Traefik routes only the intended hostname.
services:
traefik:
image: traefik:v3.1
command:
- --api.dashboard=false
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --certificatesresolvers.le.acme.email=${ACME_EMAIL}
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
- --serversTransport.insecureSkipVerify=false
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik:/letsencrypt
restart: unless-stopped
postgres:
image: postgres:16
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./database:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
appsmith:
image: appsmith/appsmith-ce:v1.36
env_file:
- .env
environment:
APPSMITH_ENCRYPTION_PASSWORD: ${APPSMITH_ENCRYPTION_PASSWORD}
APPSMITH_SUPERUSER_PASSWORD: ${APPSMITH_SUPERUSER_PASSWORD}
APPSMITH_DB_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
APPSMITH_REDIS_URL: redis://redis:6379
APPSMITH_MONGODB_URI: mongodb://mongo:27017/appsmith
depends_on:
postgres:
condition: service_healthy
labels:
- traefik.enable=true
- traefik.http.routers.appsmith.rule=Host(`${DOMAIN}`)
- traefik.http.routers.appsmith.entrypoints=websecure
- traefik.http.routers.appsmith.tls=true
- traefik.http.routers.appsmith.tls.certresolver=le
- traefik.http.services.appsmith.loadbalancer.server.port=80
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
mongo:
image: mongo:7
volumes:
- appsmith_mongo:/data/db
restart: unless-stopped
volumes:
appsmith_mongo: {}
If the copy button does not work in your browser/editor, manually select the command block and copy it.
cat > /opt/appsmith/docker-compose.yml <<'EOF'
# (paste the compose file from previous block)
EOF
cd /opt/appsmith
docker compose --env-file .env config > /tmp/appsmith.rendered.yml
docker compose --env-file .env up -d
If the copy button does not work in your browser/editor, manually select the command block and copy it.
3) Validate DNS, TLS, and first login
After containers are up, validate certificate issuance and route behavior before handing the URL to end users.
docker compose -f /opt/appsmith/docker-compose.yml ps
curl -I https://apps.example.com
openssl s_client -connect apps.example.com:443 -servername apps.example.com </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -dates
docker logs $(docker ps --format '{{.Names}}' | grep -m1 traefik) --since 10m | tail -100
If the copy button does not work in your browser/editor, manually select the command block and copy it.
Configuration and secret-handling best practices
Use the following operational guardrails from day one:
- Never store secrets in compose YAML: keep them in
.envwithchmod 600. - Rotate credentials quarterly: PostgreSQL password, Appsmith encryption secret, and admin credentials.
- Separate backup credentials: backup destination tokens should not equal runtime app credentials.
- Pin image tags: avoid
latestfor deterministic rollbacks. - Use maintenance windows: perform upgrades with tested rollback command history.
# Example: safe credential rotation workflow
cd /opt/appsmith
cp .env .env.bak.$(date +%F)
NEW_DB_PASS=$(openssl rand -base64 36 | tr -d '\n')
sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=${NEW_DB_PASS}|" .env
# Apply in controlled window
docker compose --env-file .env up -d postgres
sleep 8
docker compose --env-file .env up -d appsmith
# Verify app health after rotation
curl -fsS https://apps.example.com >/dev/null && echo "Appsmith reachable"
If the copy button does not work in your browser/editor, manually select the command block and copy it.
Verification checklist
- HTTPS endpoint serves valid certificate and redirects HTTP to HTTPS.
- All containers healthy and restarting policy set to
unless-stopped. - Appsmith login succeeds with admin account; test app can be created and saved.
- Database backup job executes and restore test completes in staging.
- Firewall allows only expected ingress ports.
# Basic smoke checks
cd /opt/appsmith
docker compose ps
docker exec -i $(docker compose ps -q postgres) psql -U appsmith -d appsmith -c "select now();"
# Backup test
mkdir -p /opt/appsmith/backups
TS=$(date +%F_%H%M)
docker exec -i $(docker compose ps -q postgres) pg_dump -U appsmith appsmith | gzip > /opt/appsmith/backups/appsmith_${TS}.sql.gz
ls -lh /opt/appsmith/backups | tail -3
If the copy button does not work in your browser/editor, manually select the command block and copy it.
Common issues and fixes
Traefik does not issue certificate
Usually caused by DNS mismatch or blocked port 80. Confirm A record and ensure temporary HTTP challenge path is reachable from internet. Inspect Traefik logs for ACME errors.
Appsmith loads but cannot save apps
Check PostgreSQL connectivity and credentials in .env. A typo in APPSMITH_DB_URL can allow partial UI load while writes fail. Verify database health check status.
Slow editor performance
Increase VM CPU/RAM and check host disk latency. Also verify there is no swap pressure. For larger teams, split observability and backup tasks off-host and keep Appsmith node focused on runtime.
Upgrade caused regressions
Rollback with pinned previous image tag and restore matching database snapshot if schema change was applied. Never upgrade without a tested backup from the same maintenance window.
FAQ
Can I run Appsmith without PostgreSQL?
You can run default bundled dependencies, but a dedicated PostgreSQL service improves control, backup procedures, and predictable operations in production.
Should I expose Appsmith directly on port 80/443?
Use Traefik (or another reverse proxy) in front of Appsmith. It centralizes TLS, request routing, and future policy controls like IP allowlists and middleware.
How often should backups run?
At least daily for most teams; high-change environments may need hourly dumps plus WAL archiving. Pair this with periodic restore tests, not just backup creation.
What is the safest way to upgrade?
Pin image versions, create a fresh backup, run upgrade in a maintenance window, verify smoke tests, and keep rollback commands documented in your runbook.
Can I integrate SSO later?
Yes. Start with local admin bootstrap, then move to centralized identity once baseline operations are stable. Plan role mappings before rollout to business users.
How do I prevent secret leakage?
Never post secrets in ticket comments or chat, avoid plaintext in compose files, rotate regularly, restrict file permissions, and use a vault for long-term secret governance.
Related guides on SysBrix
- Deploy OpenProject with Docker Compose + Caddy + PostgreSQL
- Deploy RabbitMQ with Docker Compose + Caddy
- Deploy Directus with Docker Compose + Caddy + PostgreSQL
Talk to us
Need help deploying Appsmith in production, integrating secure internal workflows with your existing data systems, or building backup and upgrade runbooks your team can trust? We can help with architecture, hardening, migration, and operational readiness.