Enterprise-grade, self-hosted zero-knowledge secret sharing.
Share passwords, API keys, and sensitive files via one-time secure links —
fully self-contained with Docker Compose. No cloud dependency. No data leaves your server.
- What You Need
- Prerequisites
- Quick Install
- Pin to a Specific Version
- SSL & Reverse Proxy
- Administration
- Clean Reinstall
- Container Architecture
- Environment Variables
- Security Highlights
- Troubleshooting
Just one file. The installer is self-bootstrapping — it auto-fetches all required library modules from GitHub before installation begins.
kisa-ops/GoSecureShare
└── install.sh ← The only file you need to download
| Requirement | Minimum version | How to install |
|---|---|---|
| Docker Engine | 24+ | curl -fsSL https://get.docker.com | sh |
| Docker Compose plugin | v2 | apt-get install -y docker-compose-plugin |
curl |
any | apt-get install -y curl |
openssl |
any | apt-get install -y openssl |
PostgreSQL is not required on the host. It runs as a dedicated container inside the Docker stack.
mkdir gosecureshare && cd gosecureshare
curl -fsSL https://raw.githubusercontent.com/kisa-ops/GoSecureShare/main/install.sh -o install.sh
chmod +x install.shsudo ./install.shThe interactive wizard guides you through every step:
- ✅ GHCR credentials — prompt for GitHub PAT (speak to contact.us@gosecureshare.com)
- ✅ Prerequisites — Docker, Compose,
curl, andopensslare verified - 🔍 Version — latest stable release auto-detected from GitHub Releases
(override:
GSS_VERSION=x.y.z sudo ./install.sh) - 🐳 Image pull — all 6 container images pulled from GHCR and Docker Hub
- 📁 Install directory —
/opt/gosecureshare/created with all runtime files - 🔗 DB files —
db/init.sqlanddb/docker-migrate.shfetched from GitHub - ⚙️ Configuration — ports, admin email, and admin password prompted interactively
- 🔐 Secrets — AES-256 encryption key, JWT secret, and DB passwords auto-generated
- 🔒 SSL — choose between your own reverse proxy or providing certificate files for host-level Nginx TLS termination
- 📝
.envwritten — secured at/opt/gosecureshare/.env(chmod 600, root-only) - 🚀 Stack started — all 8 containers started via Docker Compose
- ⏳ DB migration — idempotent schema bootstrap waits to complete
- ❤️ Health checks — all containers polled until healthy
- ✔️ Login verified — platform login endpoint confirmed reachable before success
After installation, the summary screen displays your URLs:
| Interface | Internal host port | Purpose |
|---|---|---|
| Platform (Admin / Staff) | 8181 |
Login · create secrets · admin dashboard |
| Recipient Portal | 80 |
View a shared one-time secret link |
These internal ports sit behind your reverse proxy or host Nginx, which terminates TLS and forwards to them. See SSL & Reverse Proxy below.
GSS_VERSION=2.3.1 sudo ./install.shVersion pinning controls which GHCR image tags are pulled:
ghcr.io/kisa-ops/gosecureshare-api-platform:2.3.1
ghcr.io/kisa-ops/gosecureshare-api-recipient:2.3.1
ghcr.io/kisa-ops/gosecureshare-frontend-platform:2.3.1
ghcr.io/kisa-ops/gosecureshare-frontend-recipient:2.3.1
The installer presents two SSL options during setup:
Choose this if you already have Cloudflare, Nginx, HAProxy, or any other TLS-terminating
proxy in front of the server. Docker binds its ports to 127.0.0.1 only; your proxy
handles HTTPS and forwards traffic:
| Proxy target | Internal upstream | External port |
|---|---|---|
| Platform (admin / staff UI) | http://127.0.0.1:8181 |
8443 (HTTPS) |
| Recipient (share portal) | http://127.0.0.1:8282 |
443 (HTTPS) |
Port mapping summary: the proxy listens on the external port, terminates TLS, and forwards plain HTTP to the internal upstream. End users always access the platform on
https://platform.anoudapps.com:8443and the recipient portal onhttps://share.anoudapps.com.
Choose this if GoSecureShare manages TLS directly on the host. The installer configures a host Nginx instance as a TLS terminator. You will be prompted to provide a certificate, private key, and (optionally) a CA bundle — either as a file path or by pasting the PEM content — separately for the Platform and Recipient endpoints.
All day-to-day administration is performed using the management scripts installed at
/opt/gosecureshare/. Always run them as root from that directory.
/opt/gosecureshare/
├── .env ← All secrets & config (chmod 600, root-only)
├── docker-compose.yml ← Generated by install.sh — do not edit manually
├── upgrade.sh ← Upgrade to a newer version (auto-rollback on failure)
├── stop.sh ← Graceful shutdown for maintenance windows
├── start.sh ← Start / resume after maintenance
├── backup.sh ← Full backup: database + config + scripts
├── db/
│ ├── docker-migrate.sh ← Idempotent DB schema bootstrap (runs on every up)
│ └── init.sql ← Intentional no-op (schema managed by docker-migrate.sh)
├── nginx/
│ ├── platform.conf ← Nginx reverse proxy config for Platform UI
│ └── recipient.conf ← Nginx reverse proxy config for Recipient portal
└── backups/ ← Timestamped backup archives (auto-pruned)
└── gss-backup-<ver>-<ts>.tar.gz
upgrade.sh upgrades GoSecureShare with full safety guarantees. If health checks fail
after applying new images, it automatically rolls back to the previously running version.
| Step | What happens |
|---|---|
| 1 | Pre-flight: Docker daemon running, ≥2 GB free disk, GHCR reachable, stack running |
| 2 | Snapshot: docker-compose.yml and .env copied to .upgrade-snapshot/ |
| 3 | DB backup: full pg_dump written to .upgrade-snapshot/db-backup-<version>.sql |
| 4 | Pull first: all 4 new images pulled completely before the live stack is touched |
| 5 | Apply: image tags patched in docker-compose.yml, stack restarted |
| 6 | Health checks: all 4 app containers polled (120 s timeout per container) |
| 7 | Auto-rollback: on any failure, snapshot restored and old images re-applied |
| 8 | Version pin: GSS_INSTALLED_VERSION in .env updated only after all checks pass |
cd /opt/gosecureshare
# Upgrade to the latest stable release
sudo ./upgrade.sh
# Upgrade to a specific version
sudo ./upgrade.sh 2.5.0The installer prompts for GitHub PAT (speak with contact.us@gosecureshare.com).
/opt/gosecureshare/.upgrade-snapshot/
├── docker-compose.yml.<previous-version> ← previous compose file
├── .env.<previous-version> ← previous .env (chmod 600)
└── db-backup-<previous-version>.sql ← full database dump
These files are never automatically deleted — keep them until you are confident the upgrade is stable.
# 1. Restore configuration files
cp /opt/gosecureshare/.upgrade-snapshot/docker-compose.yml.<version> \
/opt/gosecureshare/docker-compose.yml
cp /opt/gosecureshare/.upgrade-snapshot/.env.<version> \
/opt/gosecureshare/.env
chmod 600 /opt/gosecureshare/.env
# 2. Restart on the old version
cd /opt/gosecureshare && sudo docker compose up -d
# 3. Optionally restore the database
docker exec -i gosecureshare-postgres psql -U gss_superuser gosecureshare \
< /opt/gosecureshare/.upgrade-snapshot/db-backup-<version>.sqlstop.sh gracefully shuts down the entire stack in the correct order, ensuring no data
is lost and in-flight requests are drained before containers stop.
| Step | Action |
|---|---|
| 1 | Stops host Nginx (if running in SSL cert mode); leaves a marker for start.sh |
| 2 | Waits a drain period for in-flight requests to complete (default: 10 s) |
| 3 | Stops app containers in safe order: nginx → frontend → API |
| 4 | Stops PostgreSQL last, after all app containers are fully down |
| 5 | Prints final docker compose ps status table |
cd /opt/gosecureshare
# Standard graceful stop
sudo ./stop.sh
# Skip drain wait (emergency stop)
GSS_DRAIN_SECONDS=0 sudo ./stop.sh
# Longer drain for busy servers
GSS_DRAIN_SECONDS=30 sudo ./stop.shPostgreSQL volumes are not removed. All data is fully preserved.
start.sh starts the full stack and confirms all services are healthy before returning
control to the terminal.
| Step | Action |
|---|---|
| 1 | Pre-flight: Docker daemon running, .env and docker-compose.yml present |
| 2 | docker compose up -d — starts all containers |
| 3 | Resumes host Nginx if the stop.sh marker is present; runs nginx -t first |
| 4 | Polls all 4 app containers until healthy (120 s timeout); exits with error if any fail |
| 5 | Prints Platform and Recipient access URLs on success |
cd /opt/gosecureshare && sudo ./start.sh# 1. Back up before starting work
sudo ./backup.sh
# 2. Stop the stack
sudo ./stop.sh
# 3. Perform maintenance (OS patches, disk resize, certificate renewal, etc.)
# 4. Start the stack and verify health
sudo ./start.shbackup.sh creates a complete, timestamped, self-contained backup archive. The archive
holds everything needed to fully restore a fresh installation from scratch.
| Artifact | File in archive | Notes |
|---|---|---|
| Database | database.sql.gz |
Full pg_dump, gzip-compressed |
| Environment | config/.env |
chmod 600 preserved in archive |
| Compose file | config/docker-compose.yml |
Exact image tags and port config |
| Nginx configs | config/nginx/platform.conf, recipient.conf |
Reverse proxy routing rules |
| DB bootstrap | db/docker-migrate.sh, db/init.sql |
Schema + seed for a fresh DB |
| Management scripts | scripts/upgrade.sh, start.sh, stop.sh, backup.sh |
All admin scripts |
| Manifest | MANIFEST.txt |
Version, timestamp, file list |
/opt/gosecureshare/backups/
└── gss-backup-<version>-<timestamp>.tar.gz ← chmod 600 (root-only)
Old archives are pruned automatically. Default: keep the 7 most recent. Override:
cd /opt/gosecureshare
# Standard backup
sudo ./backup.sh
# Keep last 14 backups
GSS_BACKUP_KEEP=14 sudo ./backup.sh
# Write backups to an external mount
GSS_BACKUP_DIR=/mnt/nas/gss-backups sudo ./backup.sh
# Skip gzip compression (faster, larger files)
sudo ./backup.sh --no-compressAdd to /etc/cron.d/gosecureshare-backup for a daily backup at 02:00:
0 2 * * * root /opt/gosecureshare/backup.sh >> /var/log/gss-backup.log 2>&1
# 1. Extract the dump from the archive
tar -xzf /opt/gosecureshare/backups/gss-backup-2.5.0-20260716T020000Z.tar.gz \
--strip-components=1 '*/database.sql.gz'
# 2. Decompress
gunzip database.sql.gz
# 3. Restore (stack must be running and PostgreSQL must be healthy)
docker exec -i gosecureshare-postgres psql \
-U gss_superuser gosecureshare < database.sql| Task | Command |
|---|---|
| Upgrade to latest | cd /opt/gosecureshare && sudo ./upgrade.sh |
| Upgrade to version | sudo ./upgrade.sh 2.5.0 |
| Stop for maintenance | sudo ./stop.sh |
| Start after maintenance | sudo ./start.sh |
| Take a manual backup | sudo ./backup.sh |
| Check container status | sudo docker compose ps |
| Tail all logs | sudo docker compose logs -f |
| Tail one service | sudo docker compose logs -f api_platform |
| View DB migration log | sudo docker logs gosecureshare-db-migrate |
| Health check — Platform | curl http://localhost:8181/healthz |
| Health check — Recipient | curl http://localhost:8282/healthz |
| Restart single container | sudo docker compose restart api_platform |
| Open DB shell | sudo docker exec -it gosecureshare-postgres psql -U gss_superuser gosecureshare |
cd /opt/gosecureshare && sudo ./backup.shAlways take a backup before wiping. The archive is self-contained and can restore a fresh server to the same state.
cd /opt/gosecureshare
sudo docker compose down -v
⚠️ The-vflag removes all Docker volumes, including PostgreSQL data. This is required so the database migration and admin seed scripts run clean on the next install.
sudo rm -rf /opt/gosecuresharesudo docker rmi $(sudo docker images --format '{{.ID}}' \
--filter 'reference=*gosecureshare*') 2>/dev/null || true
sudo docker rmi postgres:16-alpine nginx:1.27-alpine 2>/dev/null || truecd ~/gosecureshare && sudo ./install.shcd /opt/gosecureshare
sudo docker compose ps # all containers healthy / running
sudo docker logs gosecureshare-db-migrate # should end with exit code 0
curl http://localhost:8181/healthz # → {"ok": true} (Platform)
curl http://localhost:8282/healthz # → {"ok": true} (Recipient)Host machine
└── Docker Engine
└── gss_internal (bridge network — fully self-contained)
├── gosecureshare-postgres :5434 (host) / :5432 (internal)
├── gosecureshare-db-migrate runs once, then exits (exit 0)
├── gosecureshare-nginx-platform :8181 (host) → Platform UI + API
├── gosecureshare-nginx-recipient :8282 (host) → Recipient share page
├── gosecureshare-ui-platform Next.js 14 — login + admin dashboard
├── gosecureshare-ui-recipient Next.js 14 — /s/[uuid] share page
├── gosecureshare-api-platform FastAPI — auth, secrets, admin
└── gosecureshare-api-recipient FastAPI — public reveal endpoint only
6 unique images, 8 containers:
| Image | Source | Container name |
|---|---|---|
gosecureshare-api-platform:<ver> |
ghcr.io/kisa-ops |
gosecureshare-api-platform |
gosecureshare-api-recipient:<ver> |
ghcr.io/kisa-ops |
gosecureshare-api-recipient |
gosecureshare-frontend-platform:<ver> |
ghcr.io/kisa-ops |
gosecureshare-ui-platform |
gosecureshare-frontend-recipient:<ver> |
ghcr.io/kisa-ops |
gosecureshare-ui-recipient |
postgres:16-alpine |
Docker Hub | gosecureshare-postgres + gosecureshare-db-migrate |
nginx:1.27-alpine |
Docker Hub | gosecureshare-nginx-platform + gosecureshare-nginx-recipient |
All configuration is stored in /opt/gosecureshare/.env, auto-generated and secured by
install.sh (chmod 600 — readable by root only).
| Variable | Required | Description |
|---|---|---|
POSTGRES_DB |
✅ | Database name (gosecureshare) |
POSTGRES_USER |
✅ | DB superuser username |
POSTGRES_PASSWORD |
✅ | DB superuser password |
GSS_PLATFORM_DB_USER |
✅ | Application role for the platform API |
GSS_PLATFORM_DB_PASSWORD |
✅ | Platform DB role password |
GSS_RECIPIENT_DB_USER |
✅ | Application role for the recipient API |
GSS_RECIPIENT_DB_PASSWORD |
✅ | Recipient DB role password |
ENCRYPTION_KEY |
✅ | AES-256 key — 64 hex characters, auto-generated |
JWT_SECRET |
✅ | JWT signing key, auto-generated |
GSS_ADMIN_EMAIL |
✅ | Admin account email address |
GSS_ADMIN_PASSWORD |
✅ | Admin password (minimum 12 characters) |
GSS_INSTALLED_VERSION |
✅ | Version tag pinned after successful install/upgrade |
PLATFORM_HTTP_PORT |
— | Platform internal host port (default 8181) |
RECIPIENT_HTTP_PORT |
— | Recipient internal host port (default 8282) |
LDAP_ENABLED |
— | Enable LDAP / Active Directory auth (false) |
DEBUG |
— | Enable debug mode — always false in production |
⚠️ Never share or commit.env. It contains all secrets for your installation.
- 🔒 Every secret is encrypted with AES-256-GCM before storage — encryption keys never leave your server
- 🔥 Burn-after-reading: secrets are deleted from the database immediately after the first successful reveal
- 🧱 Recipient Nginx blocks all
/api/admin routes — the admin interface is completely unreachable via the recipient portal port - 🔑 Passwords are hashed with Argon2id
- 🧹 Automatic retention sweeper deletes stale secrets (default: 14 days, configurable 1–90 days)
- 📋 All access attempts are written to
gss_recipient.audit_logs - 🚫 Set
DEBUG=falseand restrictCORS_ORIGINS_RAWfor production hardening
permission denied on Docker socket
sudo usermod -aG docker $USER && newgrp dockerContainers not starting
sudo docker compose -f /opt/gosecureshare/docker-compose.yml logsPort conflict
sudo lsof -i :8181
sudo lsof -i :8282Re-run install.sh and choose different ports, or edit PLATFORM_HTTP_PORT /
RECIPIENT_HTTP_PORT in .env and restart:
sudo docker compose -f /opt/gosecureshare/docker-compose.yml up -dGHCR pull failed
Images require a GitHub PAT with read:packages scope. The installer collects this
interactively. If a pull fails, verify your PAT has not expired and has the correct scope
at GitHub → Settings → Developer settings → Personal access tokens.
DB file fetch failed
If init.sql or docker-migrate.sh could not be fetched during bootstrap, verify your
PAT includes the repo scope in addition to read:packages, then re-run the installer:
sudo ./install.shContainer unhealthy
# Platform API + UI
curl http://localhost:8181/healthz
# Expected: {"ok": true}
# Recipient API + UI
curl http://localhost:8282/healthz
# Expected: {"ok": true}Login fails after fresh install
# 1. Confirm DB migration completed cleanly
sudo docker logs gosecureshare-db-migrate
# 2. Confirm admin seed ran
sudo docker logs gosecureshare-api-platform | grep -i seed
# 3. If in doubt, perform a clean reinstall
cd /opt/gosecureshare && sudo docker compose down -v
sudo rm -rf /opt/gosecureshare
# Then re-run: sudo ./install.shCertificate renewal (SSL cert mode)
# Test Nginx config before reloading
nginx -t
# Reload Nginx to pick up the new certificate (zero downtime)
nginx -s reloadBuilt with ❤️ by the GoSecureShare team.