Foglet does not ship a complete VPS deployment bundle yet. There is no committed
systemd unit, reverse-proxy config, firewall script, or bare-metal install
script. This page describes the supported shape for operators who want to adapt
the OTP release to a single Linux host without pretending that a turnkey VPS
runbook exists.
If you need the lowest-friction path today, use the Docker image or the committed Fly.io configuration. If you run a VPS, keep your local runbook with the host. The repo is not the only source of truth for your firewall, TLS, backups, or OS hardening.
Supported shape
A VPS deployment should look like this:
- one Linux host
- Postgres reachable by
DATABASE_URL - one Foglet OTP release running as a non-root application user
- persistent storage for SSH host keys and any operator-managed door files
- SSH daemon bound to an internal or public port of your choice
- Phoenix endpoint bound to an HTTP port behind TLS termination or a reverse proxy
- a service manager such as
systemdto restart the release after crashes or host reboot
Phoenix is supporting infrastructure. Do not frame the reverse proxy as the main product surface; callers dial in over SSH.
Build or obtain a release
The Dockerfile is the committed production image path. For a bare release, build
an OTP release with the repository's Elixir/Erlang versions and copy the
_build/prod/rel/foglet_bbs output to the host.
MIX_ENV=prod mix deps.get --only prod
MIX_ENV=prod mix compile
MIX_ENV=prod mix release
The release contains bin/foglet_bbs. The checked-in release overlay also
provides bin/server and bin/migrate scripts in release builds.
Runtime environment
Set at least:
DATABASE_URL='ecto://USER:PASS@HOST/DATABASE'
SECRET_KEY_BASE='<generated-secret-key-base>'
PHX_SERVER='true'
PHX_HOST='bbs.example.net'
PORT='4000'
FOGLET_SSH_PORT='2222'
SSH_HOST_KEY_DIR='/srv/foglet/ssh'
Generate the secret with:
mix phx.gen.secret
Store these values in your service manager's environment file or secret store. Do not commit them. Do not put them in Foglet's DB-backed configuration table.
Host-key setup
Generate production SSH host keys once and keep them on persistent, access-controlled storage:
install -d -o foglet -g foglet -m 0700 /srv/foglet/ssh
ssh-keygen -t ed25519 -f /srv/foglet/ssh/ssh_host_ed25519_key -N ''
chown foglet:foglet /srv/foglet/ssh/ssh_host_ed25519_key /srv/foglet/ssh/ssh_host_ed25519_key.pub
chmod 0600 /srv/foglet/ssh/ssh_host_ed25519_key
Back up this directory. A changed host key tells every returning caller that the server identity changed.
Service manager sketch
No unit file is committed. A minimal systemd unit would need to set the runtime
environment, run as the foglet user, start the release, and restart on failure.
Treat this as a sketch, not a pasted production policy:
[Unit]
Description=Foglet BBS
After=network-online.target
Wants=network-online.target
[Service]
User=foglet
Group=foglet
WorkingDirectory=/srv/foglet/current
EnvironmentFile=/etc/foglet/foglet.env
ExecStart=/srv/foglet/current/bin/server
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Add host hardening deliberately. Options such as NoNewPrivileges,
ProtectSystem, PrivateTmp, cgroup limits, and restricted door users affect
Door Games and file access. Test them with the exact features you intend to run.
Reverse proxy and firewall
At minimum:
- forward public HTTPS to the Phoenix
PORTfor docs and/up - expose the Foglet SSH port you chose, often public
22or2222 - block direct database access from the public network
- keep the release user's SSH host-key directory private
The app does not enable TLS itself by default in production. config/runtime.exs
contains a commented Phoenix HTTPS example, but the committed Fly deployment
terminates TLS in front of the app. Most VPS deployments should do the same with
a reverse proxy or load balancer.
Health check:
curl -fsS https://bbs.example.net/up
SSH check:
ssh -p 2222 bbs.example.net
Use the port you actually exposed.
Migrations and seeds
Run release-safe setup from the deployed release:
/srv/foglet/current/bin/foglet_bbs eval FogletBbs.Release.seed
That runs migrations and production-safe seeds. Do not run
priv/repo/seeds.exs on a real VPS instance; it creates demo accounts and
sample content.
For rollback, prefer a forward fix. If you must roll back the schema, verify a backup and run:
/srv/foglet/current/bin/foglet_bbs eval 'FogletBbs.Release.rollback(FogletBbs.Repo, 20260101000000)'
Use the actual migration version you intend to roll back to.
Backups
Carry at least:
- Postgres backups with restore drills.
/srv/foglet/sshor your chosenSSH_HOST_KEY_DIR.- release tarballs or image references for rollback.
/etc/foglet/foglet.envor whatever secret store replaces it.- door manifests and persistent door state if you enable Door Games.
Any destructive restore, schema rollback, or manual data repair should name the backup and target migration before it starts.
Door Games on a VPS
A VPS can support the stronger external-door baseline better than the current Docker/Fly image, because the operator can create a distinct OS user for door processes.
If you enable untrusted external or classic doors:
- keep the Foglet app user and door user separate, such as
fogletandfoglet-door - keep door files absolute, operator-owned, and narrowly executable
- prevent the door user from reading app secrets, database URLs, SMTP passwords, SSH host private keys, and backups
- verify timeout and disconnect cleanup before opening the door to callers
If you cannot prove the sandbox setup, disable untrusted doors rather than running them as the app user.