This guide takes you from a fresh VPS or home server to a HomeCloud you can reach over HTTPS, that starts at boot, is backed up and can be upgraded. It targets Ubuntu 24.04 and Debian 12 on amd64 and arm64; other systemd-based distributions work the same way once Docker is installed.
Everything below uses commands and flags that exist in the homecloud binary
(homecloud <command> --help lists them). For a laptop install, the quick start
is enough.
- What you need
- Install Docker
- Install HomeCloud
- First start
- Run it as a service
- Expose it safely
- Firewall
- DNS
- Use it from your laptop, the AWS CLI and Terraform
- Backups and restore
- Upgrades
- KVM and VM instances
- Security notes
- Troubleshooting
HomeCloud is one Go binary. Every service it offers (instances, S3, databases, functions, queues, ...) runs as a container on the Docker Engine of the same machine, so the machine's size is the size of everything you run in it.
| Minimum to try it | Comfortable | |
|---|---|---|
| CPU | 2 vCPUs | 4+ vCPUs |
| Memory | 4 GB | 8-16 GB |
| Disk | 30 GB | 100 GB+ SSD (images, volumes, S3 objects and database data all live in Docker) |
| Architecture | amd64 or arm64 |
These are recommendations, not limits the software enforces. The only checks are in homecloud doctor,
which fails when Docker reports less than 2 GB of memory and advises at least 4 GB for databases and
functions. The smallest instance type, t3.nano, is 0.5 vCPU and 512 MB; r5.large is 2 vCPUs and
16 GB. Add up the instances, databases and function environments you plan to run.
Other requirements:
- A 64-bit Linux with systemd, and
curl. - Outbound internet access. HomeCloud pulls container images (MinIO for S3, database engines, the AWS Lambda runtime images, OS images for instances) and, on first use, builds a small helper image used to enforce security groups.
- A DNS name pointing at the server if you want HTTPS with a real certificate (see DNS).
- One HomeCloud installation per Docker host: the server refuses to start against containers that belong to another HomeCloud account.
HomeCloud needs Docker Engine (not Podman). Install it from Docker's package repository, or use the convenience script that the HomeCloud installer itself suggests:
curl -fsSL https://get.docker.com | sudo shTo follow the repository route instead, see Docker's guides for Ubuntu and Debian.
Let your normal user talk to Docker (log out and back in afterwards, or run newgrp docker):
sudo usermod -aG docker "$USER"
sudo systemctl enable --now docker
docker run --rm hello-worldRun HomeCloud as this normal user rather than as root: the data directory is created under the user's
home (~/.homecloud) and the service (below) runs as that user.
curl -fsSL https://homecloud.pages.dev/scripts/install.sh | sh
homecloud versionThe script detects linux and amd64/arm64, downloads the latest release from GitHub, verifies its
SHA-256 checksum, and installs /usr/local/bin/homecloud (using sudo when that directory is not
writable). If cosign is installed, it
also verifies the release's Sigstore signature (releases from v0.5.0 on are signed by the release
workflow) and refuses to install on a bad or missing signature; without cosign it says it checked the
checksum only. To verify a release by hand, see Verifying releases.
Without Docker it still installs the CLI, and says that the server needs Docker. Two environment
variables change its behavior:
# pin a release, or install somewhere else
curl -fsSL https://homecloud.pages.dev/scripts/install.sh | HOMECLOUD_VERSION=v0.3.0 INSTALL_DIR="$HOME/.local/bin" shThen check the machine:
homecloud doctorAt this point it reports the server as not running, which is expected; the Docker section should be green.
Instead of the binary, you can run the official image, ghcr.io/solinode/homecloud (linux/amd64 and
linux/arm64; tags latest and one per release, e.g. 0.4.0). It runs homecloud serve and manages
the host's Docker through the mounted socket, so the services it starts (MinIO, functions, instances,
databases) are containers next to it on the same host:
docker run -d --name homecloud --restart unless-stopped \
-p 127.0.0.1:8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v homecloud-data:/data \
ghcr.io/solinode/homecloud
docker logs homecloud # first start: the root console password, shown once
eval "$(docker exec homecloud homecloud aws-env)" # AWS CLI and SDKs on this machine
docker exec homecloud homecloud ec2 ls # the homecloud CLI, inside the containerWhat to know:
-
The socket is root on the host. Whoever controls the container controls the Docker host, exactly like the
homecloudbinary run by a member of thedockergroup. The entrypoint runs the server as the unprivilegedhomeclouduser (uid 10001), added to the group that owns the socket; setHOMECLOUD_RUN_AS_ROOT=1to keep root. -
Data lives in
/data(thehomecloud-datavolume above, or a bind mount). It holds the state, the credentials file andconfig.json; flags you pass after the image name (... ghcr.io/solinode/homecloud serve --public-url https://cloud.example.com) are saved there as with the binary. The services' own data is in Docker volumes on the host, as usual. -
Address. Inside the container the API listens on
0.0.0.0:8080(HOMECLOUD_ADDR), and the-pflag decides who reaches it. Publish on127.0.0.1unless other machines need it; to expose it, put a reverse proxy in front (section 6) and set--public-url, or publish0.0.0.0:8080:8080with--tls-self-signed. If you publish on another host port (-p 127.0.0.1:9090:8080), pass--public-url http://localhost:9090so links HomeCloud generates (API Gateway, function URLs, queue URLs) point at it. -
Workloads reach the API at the HomeCloud container's own address: it joins every VPC network at a reserved address (the third-to-last of the VPC range) and the default bridge, and functions, tasks and instances get
host.docker.internalmapped to it. Nothing extra needs publishing. HomeCloud reaches MinIO, the registry, the DNS server and function environments directly over those networks. -
Published ports of instances, load balancers, MinIO (
127.0.0.1:9500), the registry (localhost:5500) and DNS (127.0.0.1:8053) are on the host, as with the binary. -
Host networking (
--network host) also works: HomeCloud then behaves exactly like the binary on the host. Passserve --addr 127.0.0.1:8080there unless you want the API on every host address. -
One installation per Docker host. Like the binary, the container refuses to start when the host runs another installation's containers.
-
Upgrade:
docker pull ghcr.io/solinode/homecloud, then recreate the container with the same volume; data is migrated at start. Take ahomecloud backupfirst: an older image refuses to start on data a newer one has used, so going back means restoring that backup. Images are signed; see Verifying releases forcosign verify. -
Backup and restore (section 10) stream through stdin and stdout. The backup includes the Docker volumes (S3 objects, databases, registry):
docker exec homecloud homecloud backup -o - > homecloud-backup.tar.gz # restore with the HomeCloud container stopped; --force moves the existing # data directory aside (to /data.before-restore-<time>) and replaces volumes: docker stop homecloud docker run -i --rm -v /var/run/docker.sock:/var/run/docker.sock -v homecloud-data:/data \ ghcr.io/solinode/homecloud restore --force - < homecloud-backup.tar.gz docker start homecloud
-
The
homecloud serviceandhomecloud upgradecommands are for the binary; with the image, Docker's restart policy and image tags do their jobs.
Start the server once in the foreground, with the settings you want to keep. Settings passed as flags
are saved to <data-dir>/config.json and reused on every later start (including by the service), so you
only pass them once. The ones you need on a server are --public-url (when the API is reached through
a reverse proxy or on a non-default port), --public-host and, when you are not putting a reverse proxy
in front, --addr:
homecloud serve --public-url https://cloud.example.com--public-url is the base URL clients reach the API at. Every link HomeCloud generates for clients
(API Gateway endpoints, Lambda function URLs, queue URLs, static website URLs, Cognito issuers,
presigned S3 URLs) starts with it, so they point at your proxy on 443 rather than at the API's own port.
Without it they are built as <scheme>://<public-host>:<api port>, with https when --tls-cert or
--tls-self-signed is used. Containers you run keep using the internal address for AWS_ENDPOINT_URL.
--public-host is the name clients use to reach what HomeCloud publishes on host ports: published
instance and database ports, load balancer ports, the DNS name server. It defaults to localhost, which is
wrong for any remote use; when --public-url is set and --public-host is not, the host name of the URL
is used. Use a DNS name (or the server's IP address).
On the very first start HomeCloud creates the account and prints, once:
first start: created account 123456789012
console sign-in user: root password: ...
CLI credentials written to /home/you/.homecloud/credentials
(the password is shown only once; reset it with `homecloud serve --reset-root-password`, or choose one with `homecloud admin set-root-password` while the server is stopped)
Copy the password now, or set one you prefer (see Security notes). The CLI
credentials (an access key for the root user) are written to ~/.homecloud/credentials (mode 0600).
When the server listens on a wildcard address such as 0.0.0.0:8080, the file records 127.0.0.1 (or
the --public-url) as the endpoint (homecloud aws-env gives AWS tools a loopback address as
localhost, see below). The data directory defaults to ~/.homecloud; override it with
--data-dir or the HOMECLOUD_DATA_DIR environment variable.
By default the API and console listen on 127.0.0.1:8080 only, so nothing is reachable from outside
yet. You can check from the server:
curl http://127.0.0.1:8080/api/v1/healthPress Ctrl-C to stop it, then continue with the service. (If you forget the password, see Troubleshooting.)
Install a systemd system unit. Run it with sudo from your normal user; under sudo the data directory
defaults to the invoking user's ~/.homecloud (the one you just initialized), not root's. If that user's
home cannot be found the command stops and asks for --data-dir.
sudo homecloud service install --systemThis writes /etc/systemd/system/homecloud.service, runs systemctl daemon-reload, and
enable --now. The unit starts at boot after docker.service, restarts on failure, and runs as the
user who invoked sudo (taken from SUDO_USER; that user must be in the docker group). It runs
homecloud serve --data-dir <dir>; every other setting is read from config.json. Use --dry-run
first to see the unit without installing it.
homecloud service status # or: systemctl status homecloud
journalctl -u homecloud -f # logs
sudo systemctl restart homecloud
sudo homecloud service uninstall # removes the unit, keeps your dataIf you ever need to change a setting, run homecloud serve with the new flag once (with the service
stopped) and start the service again, or edit config.json while it is stopped.
Without --system, homecloud service install creates a per-user unit that starts when you log in;
on a server you would also need sudo loginctl enable-linger $USER. The system unit is the better fit.
The API and console speak plain HTTP on 127.0.0.1:8080 by default. Do not simply bind it to
0.0.0.0 over the open internet without TLS: sign-in tokens and AWS access keys would travel in the
clear. Choose one of these.
Keep HomeCloud on 127.0.0.1:8080 and let Caddy obtain and renew a Let's
Encrypt certificate. Install Caddy from its
official package repository, then put this
in /etc/caddy/Caddyfile:
cloud.example.com {
reverse_proxy 127.0.0.1:8080
}
sudo systemctl reload caddyPorts to forward or open to the internet for this setup:
- 80/tcp and 443/tcp to Caddy (port 80 is needed for the certificate challenge and redirect).
- Nothing else for the console and API. HomeCloud's API stays bound to loopback, so port 8080 is not reachable from outside.
Your DNS name must already point at the server. Tell HomeCloud where clients reach it and that it may believe the proxy's forwarding headers (Caddy runs on the same machine, so it connects from loopback):
homecloud serve --public-url https://cloud.example.com --trusted-proxies 127.0.0.1/32Containers you run (functions, instances, tasks) keep reaching the API directly over plain HTTP on the Docker bridge, which is unaffected by the proxy.
What the two settings do when a proxy terminates TLS:
--public-urlmakes every generated link (function URLs, static website URLs, Cognito issuers, API Gateway endpoints, queue URLs, presigned S3 URLs) usehttps://cloud.example.cominstead of<public-host>:8080. Function URLs follow the URL's scheme.--trusted-proxiestakes a comma-separated list of CIDRs (default: none). When a request comes from one of them, the client address is taken fromX-Forwarded-For(the rightmost entry that is not itself a trusted proxy, so entries a client forges on the left are ignored) and the scheme fromX-Forwarded-Proto. IAM conditions onaws:SourceIpandaws:SecureTransport, the audit trail and the sign-in throttle then see the real client. Trust only proxies you control; without the flag the headers are ignored (details).
serve can terminate TLS itself, either with your own certificate or with a generated self-signed one.
Both need the address to listen on:
# your own PEM certificate and key (e.g. from certbot)
homecloud serve --addr 0.0.0.0:8443 --public-host cloud.example.com \
--tls-cert /etc/letsencrypt/live/cloud.example.com/fullchain.pem \
--tls-key /etc/letsencrypt/live/cloud.example.com/privkey.pem
# or a self-signed certificate (good for a home LAN or Tailscale)
homecloud serve --addr 0.0.0.0:8443 --public-host homelab.tailnet.ts.net --tls-self-signed--tls-certand--tls-keymust be given together, and the service user must be able to read both files. The certificate is loaded when the server starts, so restart the service after a renewal.--tls-self-signedcreates<data-dir>/tls/cert.pemandkey.pemonce (valid for 5 years) forlocalhost,127.0.0.1and the--public-hostvalue. Pass--public-hostin the same command that first creates it; if you change the host later, delete<data-dir>/tlsand restart.- With a self-signed certificate created at the first start, the CLI credentials record the certificate
as
ca_file, sohomecloudandhomecloud aws-env(which exportsAWS_CA_BUNDLE) trust it on this machine. Browsers will warn until you trust the certificate. - Ports below 1024 need extra privileges for an unprivileged user, so use a high port such as 8443, or use Option A for 443.
- With TLS on, functions, tasks and instances do not call the HTTPS endpoint (a certificate for your
public name cannot cover
host.docker.internal). HomeCloud opens a second, plain-HTTP listener for them on a random port, bound only to the Docker bridge gateway on Linux (an address that exists only inside the machine) or to loopback on Docker Desktop and OrbStack, and gives workloads that address asAWS_ENDPOINT_URL(http://host.docker.internal:<port>). Nothing outside the machine can reach it. Requests from workloads therefore haveaws:SecureTransportfalse, like any request over plain HTTP. - Set
--public-urltoo when clients reach the server at a name or port other than<public-host>:<api port>.
Open the chosen port (8443 above) in your firewall. The rest of the flow is the same as for Option A.
On a private network you can skip the internet altogether: bind to the LAN or Tailscale address (for
example --addr 0.0.0.0:8080 --public-host homelab.tailnet.ts.net) and, if you like, add
--tls-self-signed. Only devices on that network can reach it.
Two things are exposed on a HomeCloud host: the front door (the API and console) and whatever HomeCloud publishes on host ports.
| Port | What | Bound to |
|---|---|---|
| 22/tcp | SSH (yours) | |
| 80, 443/tcp | Caddy, if you use Option A | all interfaces |
8080/tcp (--addr) |
API, console and AWS endpoint | 127.0.0.1 by default |
9500/tcp (--s3-port) |
S3 endpoint (MinIO) | 127.0.0.1 (--s3-bind) |
9501/tcp (--s3-console-port) |
MinIO console | 127.0.0.1 (--s3-bind) |
8053/udp and tcp (--dns-port) |
DNS for public Route 53 zones | 127.0.0.1 (--dns-bind) |
| 5500/tcp | Container registry (ECR) | 127.0.0.1 only |
| dynamic | ports of instances, load balancers and public databases that security groups open | all interfaces |
The API's S3 operations already go through the main endpoint; the separate S3 and MinIO console ports
are not needed by the AWS CLI, the SDKs or the console (presigned URLs made by the console are served
through the API too, under /_s3/). Because Docker publishes ports around ufw, HomeCloud publishes
MinIO, its console and the DNS server on 127.0.0.1 by default. To expose them, pass
--s3-bind 0.0.0.0 (or one address) and --dns-bind 0.0.0.0; both are saved to config.json. Existing
installations get their MinIO and DNS containers recreated with the new binding at the next start
(their data lives in volumes and the zone records in the state file).
Docker and ufw. Docker publishes container ports by editing iptables directly, so ufw rules do
not filter them. ufw is still right for everything that is not a Docker-published port:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp # only with Caddy (Option A)
sudo ufw enableIf you did expose 9500, 9501 or 8053 with --s3-bind / --dns-bind and want them limited, the
simplest and most reliable place is your provider's network firewall (security group). On the host
itself, drop them in Docker's DOCKER-USER chain (replace eth0 with your public interface, and
repeat per port; add -p udp for 8053). The same applies to the ports of instances, load balancers and
public databases, which Docker publishes on all interfaces:
sudo iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 9501 --ctdir ORIGINAL -j DROPThose rules are not persistent across reboots by themselves; use iptables-persistent or your
provider's firewall. Then use HomeCloud's own security groups to decide which instance and database
ports are published (see architecture).
-
For the console and API: create an
A(andAAAA) record forcloud.example.compointing at the server. That name is what you give Caddy,--public-hostand the AWS endpoint. -
For Route 53 hosted zones inside HomeCloud: public zones are answered on HomeCloud's DNS port (
--dns-port, default 8053, UDP and TCP), and inside your VPCs by each VPC's resolver. The port is published on127.0.0.1unless you pass--dns-bind: to serve other machines, use--dns-bind 0.0.0.0(or the server's public IP). You can then test a zone from another machine withdig @<server-ip> -p 8053 www.example.test. -
Real delegation needs port 53, because resolvers on the internet query only that port:
homecloud serve --dns-port 53 --dns-bind <server public IP>
On Ubuntu and Debian
systemd-resolvedlistens on127.0.0.53:53, which blocks a wildcard bind, so bind to the public address as above, or turn off its stub listener (DNSStubListener=noinresolved.conf, then restartsystemd-resolved). If something already holds the port, HomeCloud refuses to start the DNS server and says which port and what to change. Docker's daemon (root) publishes the port, so the HomeCloud service does not need to run as root. Then create the NS and glue records for your domain at your registrar pointing at the server, and open 53/udp and 53/tcp. -
Instance private DNS names (
ip-10-88-0-4.internal,<db>.rds.internal) resolve only inside the VPC.
The same endpoint serves the console, the native API and the AWS wire protocols. You need an access
key: the one in ~/.homecloud/credentials on the server belongs to root; for everyday use, create a
user (see Security notes).
With the homecloud CLI on your laptop (install it with the same install script):
homecloud configure --endpoint https://cloud.example.com # prompts for the access key ID and secret
# a server with a self-signed certificate: copy its <data-dir>/tls/cert.pem here first
homecloud configure --endpoint https://homelab:8443 --ca-file ./homelab-cert.pem
homecloud whoami
eval "$(homecloud aws-env)" # prints export lines; add --fish for fish syntax
aws s3 lshomecloud aws-env prints AWS_ENDPOINT_URL, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and
AWS_REGION (and AWS_CA_BUNDLE when the credentials name a CA file). homecloud configure keeps
the region and ca_file already in the credentials file unless you pass --region or --ca-file. The environment variables
HOMECLOUD_ENDPOINT, HOMECLOUD_ACCESS_KEY_ID and HOMECLOUD_SECRET_ACCESS_KEY override the
credentials file.
Without it, set the variables yourself:
export AWS_ENDPOINT_URL=https://cloud.example.com
export AWS_ACCESS_KEY_ID=HCIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=us-east-1
aws sts get-caller-identityTerraform and OpenTofu use the standard hashicorp/aws provider with its endpoints {} block
pointing at HomeCloud (or just AWS_ENDPOINT_URL); the credentials come from the same environment
variables. Use a host name, not an IP address, in the endpoint (localhost for a local server, as
homecloud aws-env prints it): S3 Control, which the provider calls for every bucket, puts the account ID
in front of the host. See examples/terraform/shop and
aws-compat.md for the supported services.
homecloud backup downloads a backup from the running server through the API, to the machine where you
run it (the server itself, or your laptop after homecloud configure). It contains the data directory
(state, master.key, function code, logs) and every HomeCloud Docker volume: databases, S3 objects,
registry images, EBS volumes and file systems. Containers are paused briefly while their volume is
copied. Instance root disks are not included, so keep data that matters on volumes.
homecloud backup -o /var/backups/homecloud-$(date +%F).tar.gz
homecloud backup --no-volumes -o state-only.tar.gz # data directory onlyThe archive holds the master key and credentials: store it encrypted or somewhere private, and copy it
off the server. A nightly cron entry for the user that owns the credentials works (% must be escaped
in crontab):
0 3 * * * homecloud backup -o /var/backups/homecloud-$(date +\%F).tar.gz
HomeCloud does not delete old backups; add your own rotation.
Restore (on the same or a new server; Docker and the binary must be installed first):
sudo systemctl stop homecloud
homecloud restore homecloud-backup.tar.gz --data-dir "$HOME/.homecloud"
sudo systemctl start homecloudrestore refuses to run while a server answers at the address saved in the data directory's
config.json. It unpacks the data directory and recreates the Docker volumes. Use --force to move an
existing data directory aside (to <data-dir>.before-restore-<time>) and replace existing volumes, and
--no-volumes to restore only the data directory. On a new machine, run homecloud serve --public-host <name> once if the host name changed, then reinstall the service (sudo homecloud service install --system --data-dir ...).
homecloud upgrade --check # is a newer release available?
homecloud backup -o before-upgrade.tar.gz
sudo homecloud upgrade # verified download from GitHub releases; needs sudo for /usr/local/bin
sudo systemctl restart homecloud
homecloud version
homecloud doctorupgrade checks the release before replacing the binary: checksums.txt must carry a valid Sigstore
signature by this repository's release workflow for that tag, and the downloaded archive must match it
(details and the equivalent cosign verify-blob commands are in
Verifying releases). --version v0.5.0 installs a specific
release. Releases v0.4.1 and older are unsigned and install only with --allow-unsigned. Data is
migrated automatically when the new version starts. After an upgrade, the first start may need network
access to rebuild the security-group helper image.
Automatic backup and rollback. Before replacing the binary, upgrade copies the data directory's
state to <data-dir>/upgrade-backups/<old-version>-<time>/ and keeps the current binary as
<binary>.previous (for example /usr/local/bin/homecloud.previous). The copy holds what a new
version's migrations may rewrite: state.json, config.json, credentials, master.key, TLS files,
the DynamoDB database, queue, topic and state machine files and metrics. Logs, CloudTrail files, RDS
snapshot files, Lambda code and the Docker volumes (S3 objects, databases) are left out, since migrations
don't change them; that is what homecloud backup is for. The three most recent upgrade backups are kept.
The data directory is the installed system service's, $HOMECLOUD_DATA_DIR, or ~/.homecloud of the
user running sudo; pass --data-dir otherwise.
If the new version misbehaves, go back with the server stopped:
sudo systemctl stop homecloud
sudo homecloud upgrade --rollback
sudo systemctl start homecloud--rollback refuses while a server answers for the data directory. It restores the previous binary and,
if the new version has already run on the data, the state saved before the upgrade; the state it
replaces is kept in <data-dir>/upgrade-backups/rolled-back-<time>-<version>/. Changes made after the
upgrade are therefore lost in that case (resources created since then may remain as orphaned containers
or volumes). If the new version never started, the data is kept as it is.
Each start records the newest HomeCloud version that has used the data directory
(<data-dir>/data-version.json), and homecloud serve refuses to start on data written by a newer
version, since its migrations may have changed it in ways older code misreads. The message says what to
do: upgrade again, or homecloud upgrade --rollback. serve --allow-newer-data overrides the check at
your own risk.
The VM images (ami-ubuntu-24-04-vm, ami-debian-12-vm) boot a real virtual machine under QEMU, inside a
container on the instance's VPC network. Container images need nothing of this.
- KVM. With
/dev/kvmon the Docker host, guests run hardware-accelerated; without it they are emulated (works, but boots take minutes). Prefer bare metal, a home server, or a VPS with nested virtualization; check withls -l /dev/kvm(sudo apt install cpu-checker && kvm-oktells whether the CPU and firmware support it). Docker Desktop and OrbStack on macOS have no/dev/kvm. - macOS. On an Apple Silicon Mac, install QEMU (
brew install qemu) and HomeCloud runs guests on the Mac itself with Hypervisor.framework (virtualization: hvf), at full speed, with the same networking, disks and console; the VM container in Docker keeps the instance's address, firewall and volumes. Without QEMU, guests are emulated in Docker (boots take minutes).homecloud serve --vm-accel containerkeeps them in Docker;--vm-accel hvfinsists on the Mac. - Networking. passt gives the guest the instance's private address. Where passt cannot sandbox
itself, notably Ubuntu 24.04 hosts (
kernel.apparmor_restrict_unprivileged_userns=1), guests fall back to QEMU user-mode networking: NAT, no private address of their own, only the ports the security groups allow are forwarded. The instance reportsvm_network: userand the server log says why (#90). - Disk. Cloud images (about 600 MB each) are downloaded on first use into the
hc-vm-imagesvolume.
-
HomeCloud is root on the host. It controls Docker through its socket, which is root-equivalent. Treat everyone with HomeCloud administrator access as an administrator of the server, and do not run untrusted workloads with the expectation that the container boundary is a VM boundary.
-
Change the initial
rootpassword (the one printed at first start) and keep the root access key out of daily use. With the service stopped, either choose one (homecloud admin set-root-password, which asks twice without echo, or reads one line from a pipe:printf '%s\n' "$PW" | homecloud admin set-root-password; it is never taken as an argument) or generate a random one withhomecloud serve --reset-root-password. You can also manage the user's password on its IAM page in the console. -
Create IAM users instead of using the root keys. For example, an administrator for yourself and a limited user for a CI job:
homecloud iam create-user alice --password 'a-long-passphrase' --policy AdministratorAccess homecloud iam create-access-key alice # the secret is shown once homecloud iam create-user ci --policy S3ReadOnlyAccess homecloud iam create-access-key ci
Then run
homecloud configurewith the new key on your laptop.~/.homecloud/credentialson the server holds the root key: keep it mode 0600 and do not copy it around. -
Keep
master.keysafe.<data-dir>/master.keyencrypts secrets, SecureStrings, KMS key material and private CA keys. Losing it makes that data unreadable; leaking it (it is inside every backup) exposes it. Back it up with the rest, and protect the backups. -
Always use TLS for anything that leaves a trusted network (see Expose it safely). Console sign-in is throttled after 10 failures per client IP in 5 minutes, but that is no substitute.
-
Keep the API on loopback behind a proxy where you can, and firewall the ports HomeCloud publishes (Firewall).
-
Outbound requests made for users (SNS and alarm webhooks, API Gateway
HTTP_PROXYintegrations) never reach loopback, link-local (including the provider's169.254.169.254metadata service), this host's own addresses or other metadata endpoints, checked when the connection is made. Private LAN ranges stay reachable because homelabs call their own services; on a multi-user VPS setHOMECLOUD_DENY_PRIVATE_TARGETS=1in the service environment to block RFC 1918 and unique-local addresses too. -
homecloud:CreateBackupis account-root. A backup containsmaster.keyand everything it decrypts, so grant that action (andhomecloud:*) only to administrators, and encrypt backups before they leave the machine. -
Keep the host patched (
unattended-upgradeson Ubuntu and Debian) and upgrade HomeCloud regularly. -
Monitor it.
/metricsserves Prometheus metrics to an IAM user allowedhomecloud:GetMetrics(never anonymously on the API port), and the console's System status page shows Docker, helper, background loop and disk state. See Monitoring for a least-privilegeprometheususer, scrape configs, a Grafana dashboard and alert rules.
Start with:
homecloud doctorIt checks that Docker is reachable (and warns about low memory), that CLI credentials exist and are
accepted, that the server answers at the address in config.json, and that the S3, MinIO console and
registry ports are free or owned by HomeCloud. Each failure prints a suggested fix. If you use a
non-default data directory, run it with HOMECLOUD_DATA_DIR set to that directory so it reads the
right config.json and credentials.
| Symptom | What to check |
|---|---|
permission denied on /var/run/docker.sock |
The user running HomeCloud is not in the docker group (sudo usermod -aG docker <user>, then restart the service). A system unit runs as the sudo user that installed it. |
doctor says credentials are missing |
Run homecloud serve once, or homecloud configure to point at a remote server. |
| Service does not start | systemctl status homecloud and journalctl -u homecloud -n 100. Confirm docker.service is running. |
port ... is in use |
Another program uses the S3 (9500), MinIO console (9501) or DNS (8053) port. Pick others with --s3-port, --s3-console-port, --dns-port (saved to config.json). For port 53 see DNS. |
this Docker host already runs HomeCloud account ... |
Start with the --data-dir of that installation, or remove its containers first. One Docker host runs one HomeCloud. |
| Lost the root password | Stop the service, then run homecloud admin set-root-password (pick one) or homecloud serve --reset-root-password (random; copy it, stop with Ctrl-C), and start the service. |
| Browser cannot connect from outside | Confirm the proxy/port is open in the firewall and provider security group, and that curl -I https://cloud.example.com/api/v1/health works from your machine. |
| CLI or AWS CLI reports a certificate error | Use a real certificate (Caddy, or --tls-cert), or for a self-signed one copy the server's <data-dir>/tls/cert.pem to the client and run homecloud configure --ca-file <file> (AWS tools: AWS_CA_BUNDLE). |
| First instance or database is slow to start | Images are pulled and the security-group helper image is built on first use; this needs internet access. |
restore says a server is running |
Stop the service first (sudo systemctl stop homecloud). |
If none of that explains it, the server log (journalctl -u homecloud) is the place to look; open an
issue at https://github.com/solinode/homecloud/issues and include the output of homecloud doctor.