Docs
From zero to objects on your VPS.
Install, operate, and integrate Johnny with the CLI, panel, and REST API.
Overview
Johnny wraps Garage with production-ready automation: TLS via Caddy, nightly
SFTP backups with retention, a johnny CLI, and an optional Laravel web panel
with two-factor authentication.
Orchestration runs only on your primary VPS (push model). Backup servers
only need SSH/SFTP and disk space.
Requirements
- Ubuntu 24.04 LTS on the primary VPS
- DNS
A/AAAA pointing your hostname at the server before
install (Let's Encrypt)
- For each backup target: SSH/SFTP reachable from the primary, with write access under
the remote path
Installation
Prefer the autoinstaller. It installs dependencies, starts Garage, creates the
default bucket and keys, configures Caddy TLS, writes
/etc/johnny/backup.json, installs nightly cron, and can optionally install the panel.
sudo mkdir -p /opt && sudo git clone https://github.com/andreapollastri/johnny /opt/johnny
cd /opt/johnny
sudo bash scripts/autoinstall.sh
Manual install
sudo bash scripts/install.sh
sudo systemctl start johnny-garage
sudo bash scripts/bootstrap-single-node.sh
Then configure TLS yourself — see config/caddy-johnny.caddy.example or
config/nginx-johnny-s3.conf.example.
Web panel
Optional Laravel dashboard for buckets, objects, and Garage API keys. If you skipped it
during autoinstall:
sudo bash scripts/install-panel.sh /opt/johnny https://panel.example.com
Create an admin
sudo -u www-data php /opt/johnny/panel/artisan johnny:admin you@example.com 'strong-password'
Open the panel hostname, sign in, then enable TOTP 2FA under Settings.
SMTP for password reset
Default MAIL_MAILER=log only writes to the Laravel log. Set SMTP in
panel/.env, then rebuild the config cache:
sudo -u www-data php /opt/johnny/panel/artisan config:cache
CLI
| Command |
Description |
johnny version |
Print installed version |
johnny status |
Garage cluster status |
johnny update [--pull] |
Update installation |
johnny bucket list|create |
List or create buckets |
johnny key list|create |
List or create API keys |
johnny backup list|create|delete|run |
Manage SFTP backup targets |
johnny backup set-retention N |
Keep dated folders for N days (default 90) |
Prefer sudo -u johnny johnny … for key and bucket mutations that write Garage state.
Backups
Nightly job (03:00) uses rclone to sync every bucket to one or more SFTP targets under
a dated folder layout. Retention deletes YYYY-MM-DD folders older than
retention_days.
johnny-backups/
2026-04-03/
default/
my-bucket/
2026-04-04/
default/
my-bucket/
Config: /etc/johnny/backup.json (mode 600). Logs:
/var/log/johnny-nightly.log.
S3-to-S3 replication
cp config/replication/media-to-eu.env.example config/replication/media-to-eu.env
# edit credentials, then:
sudo bash scripts/replicate-run.sh config/replication/media-to-eu.env
Panel API
Secured with Laravel Sanctum personal access tokens. Interactive docs at
/api/docs, OpenAPI at /api/openapi.yaml.
sudo -u www-data php /opt/johnny/panel/artisan johnny:api-token you@example.com --name=provisioning
Provision bucket + key
curl -sS -X POST \
"https://panel.example.com/api/buckets/provision" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"bucket":"customer-orders"}'
| Method |
Path |
Description |
| GET |
/api/buckets |
List buckets |
| POST |
/api/buckets |
Create a bucket |
| GET |
/api/buckets/{bucket} |
Metadata & keys |
| DEL |
/api/buckets/{bucket} |
Delete empty bucket |
| GET / POST / DEL |
/api/keys |
List, create, lookup, delete keys |
| POST / DEL |
/api/buckets/{b}/keys… |
Grant or revoke key access |
| POST |
/api/buckets/provision |
Bucket + key + credentials in one call |
Laravel disk
Compatible with Laravel's S3 filesystem driver. Install the Flysystem adapter, paste
credentials into .env, and store files.
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=GK....123
AWS_SECRET_ACCESS_KEY=012...XYZ
AWS_DEFAULT_REGION=johnny
AWS_BUCKET=my-bucket
AWS_ENDPOINT=https://s3.my-domain.com
AWS_USE_PATH_STYLE_ENDPOINT=true
Storage::disk('s3')->put('avatars/photo.jpg', $contents);
The provisioning API returns the same keys in an env map for automated tenant onboarding.
Updates
Repo path is saved in /etc/johnny/repo.path. Nightly cron runs
johnny update --pull at 02:30.
sudo johnny update --pull
Pulls the latest release, syncs scripts, runs Composer + migrations + cache rebuild when
the panel is present, and applies numbered scripts from scripts/migrations/.
Log: /var/log/johnny-update.log.
Security
- Restrict
/etc/johnny (especially backup.json and credentials/) —
defaults are already 600/700
- Prefer SSH keys on backup servers; password auth is documented for simplicity
- Firewall: expose 443 (and 80 for ACME); restrict 22
to trusted IPs
rclone sync can delete extras under each dated prefix — review the rclone sync docs
Configuration
| Path |
Purpose |
/etc/johnny/garage.toml |
Garage configuration |
/etc/johnny/backup.json |
Backup targets and retention |
/etc/johnny/credentials/default-s3.env |
App S3 credentials |
/etc/johnny/credentials/backup-internal-s3.env |
Internal backup S3 credentials |
/etc/johnny/repo.path |
Path to the Johnny repo |
/etc/cron.d/johnny-nightly |
Update + backup schedule |
panel/.env |
Laravel panel environment |
Source and issues:
github.com/andreapollastri/johnny
· MIT License · v1.0.5