Docker Compose Permanent Operation¶
You've tried Kamerplanter in the Quick Start and now want to run it permanently? This guide shows you how to set up Kamerplanter on your own server — for example on a Raspberry Pi, NAS, or home server.
Difference from Quick Start¶
In the Quick Start, you built Kamerplanter from source code (docker compose up). For permanent operation, you use pre-built images instead — this is faster, uses less storage, and you don't need the source code on the server.
| Quick Start | Permanent deployment | |
|---|---|---|
| Docker Compose file | docker-compose.yml | docker-compose.release.yml |
| Images | Built locally | Pre-built from registry |
| Source code needed? | Yes | No |
| Restart on crash | Manual | Automatic |
| Suitable for | Trying out, development | Permanent operation |
Prerequisites¶
- A server with Docker and Docker Compose (see Installation)
- At least 2 GB RAM (4 GB recommended)
- Stable network connection for downloading images
1. Download project files¶
You only need two files from the repository — not the entire source code:
# Create directory
mkdir -p ~/kamerplanter && cd ~/kamerplanter
# Download the two required files
curl -O https://raw.githubusercontent.com/nolte/kamerplanter/main/docker-compose.release.yml
curl -O https://raw.githubusercontent.com/nolte/kamerplanter/main/.env.example
2. Create configuration¶
Open the .env file and set secure passwords:
# Generate secure passwords: openssl rand -base64 24
ARANGO_ROOT_PASSWORD=insert-secure-password-here
ARANGODB_PASSWORD=insert-secure-password-here
# Defaults — change only if needed
ARANGODB_DATABASE=kamerplanter
ARANGODB_USERNAME=root
REDIS_URL=redis://valkey:6379/0
DEBUG=false
# Without this line true applies; without outbound mail set false explicitly
REQUIRE_EMAIL_VERIFICATION=false
CORS_ORIGINS=["http://localhost:8080"]
Passwords
Do not use the example passwords from .env.example. Generate secure passwords, e.g. with openssl rand -base64 24. Both password fields (ARANGO_ROOT_PASSWORD and ARANGODB_PASSWORD) must be identical.
Four additional mandatory secrets (otherwise the backend won't start)¶
Without these four values the backend start-up aborts
.env.example sets DEBUG=false — the correct value for permanent operation. With DEBUG=false the backend additionally checks JWT_SECRET_KEY, FERNET_KEY, ERASURE_TOMBSTONE_SALT and LOG_PSEUDONYM_SALT at start-up and aborts with an error if any of them is missing (fail-fast gate). docker-compose.release.yml does not automatically pass these four variables into the backend container — you have to add them yourself. The celery-worker container now also checks FERNET_KEY and LOG_PSEUDONYM_SALT at start-up and aborts the same way if a value is missing or invalid — the celery-worker entry in the docker-compose.override.yml below is therefore mandatory, not optional. celery-beat does not check LOG_PSEUDONYM_SALT.
Generate the four values and add them to your .env:
# JWT_SECRET_KEY, ERASURE_TOMBSTONE_SALT and LOG_PSEUDONYM_SALT (at least 32 characters each)
openssl rand -hex 32
# FERNET_KEY (must be a valid Fernet key)
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
JWT_SECRET_KEY=<output of openssl rand -hex 32>
FERNET_KEY=<output of the Fernet command>
ERASURE_TOMBSTONE_SALT=<second output of openssl rand -hex 32>
LOG_PSEUDONYM_SALT=<third output of openssl rand -hex 32>
Also create a docker-compose.override.yml next to docker-compose.release.yml — Docker Compose loads it automatically:
services:
backend:
environment:
JWT_SECRET_KEY: ${JWT_SECRET_KEY}
FERNET_KEY: ${FERNET_KEY}
ERASURE_TOMBSTONE_SALT: ${ERASURE_TOMBSTONE_SALT}
LOG_PSEUDONYM_SALT: ${LOG_PSEUDONYM_SALT}
celery-worker:
environment:
FERNET_KEY: ${FERNET_KEY}
ERASURE_TOMBSTONE_SALT: ${ERASURE_TOMBSTONE_SALT}
LOG_PSEUDONYM_SALT: ${LOG_PSEUDONYM_SALT}
celery-beat:
environment:
FERNET_KEY: ${FERNET_KEY}
ERASURE_TOMBSTONE_SALT: ${ERASURE_TOMBSTONE_SALT}
Full overview of every mandatory secret per feature: Configuration Matrix — Mandatory secrets.
Reference both Compose files from now on
Since you created an additional docker-compose.override.yml, every docker compose command from here on must reference both files: -f docker-compose.release.yml -f docker-compose.override.yml. All following commands on this page assume that.
3. Set the version¶
The file docker-compose.release.yml contains __VERSION__ as a placeholder. Replace it with the desired version:
Which version should I use?
Use the latest stable version. You can find available versions on the project's Releases page.
4. Start¶
Check the status:
All services should show as running or healthy after 30–60 seconds.
5. Test access¶
Open in your browser:
- Kamerplanter: http://your-server:8080
- API documentation: http://your-server:8000/api/v1/docs
On the server itself, use localhost in place of your-server. From any other device these addresses answer only after you have opened the two ports — see Accessing from other devices; until then the ports are bound to 127.0.0.1 and another device gets "connection refused".
Automatic restart¶
The release configuration already includes restart: unless-stopped for all services. This means:
- After a server crash or reboot, Docker restarts the services automatically
- Services you deliberately stop with
docker compose stopstay stopped
To ensure Docker itself starts after a reboot:
Performing updates¶
To update Kamerplanter to a new version:
cd ~/kamerplanter
# 1. Update version in the Compose file
sed -i 's/old-version/new-version/g' docker-compose.release.yml
# 2. Pull new images and restart services
docker compose -f docker-compose.release.yml -f docker-compose.override.yml pull
docker compose -f docker-compose.release.yml -f docker-compose.override.yml up -d
# 3. Clean up old, unused images (optional)
docker image prune -f
Data is preserved
Your plants, locations, and all other data are stored in Docker volumes and survive updates without issues.
Backups¶
Kamerplanter's data lives in two Docker volumes:
arangodb_data— All plants, locations, tasks, and configurationsvalkey_data— Cache and task queue (not critical, rebuilt automatically)
Create a backup¶
# Back up ArangoDB data
docker compose -f docker-compose.release.yml -f docker-compose.override.yml exec arangodb \
arangodump --server.password "$ARANGO_ROOT_PASSWORD" \
--output-directory /tmp/backup --overwrite true
# Copy backup from the container
docker compose -f docker-compose.release.yml -f docker-compose.override.yml cp \
arangodb:/tmp/backup ./backup-$(date +%Y%m%d)
Restore a backup¶
# Copy backup into the container
docker compose -f docker-compose.release.yml -f docker-compose.override.yml cp \
./backup-20260317 arangodb:/tmp/backup
# Restore data
docker compose -f docker-compose.release.yml -f docker-compose.override.yml exec arangodb \
arangorestore --server.password "$ARANGO_ROOT_PASSWORD" \
--input-directory /tmp/backup --overwrite true
Regular backups
Set up a cron job to run backups automatically — for example daily at 3:00 AM. That way you'll lose at most one day of data in the worst case.
Accessing from other devices¶
By default, Kamerplanter is only accessible from the server itself: docker-compose.release.yml binds every port to 127.0.0.1. To access it from your smartphone, tablet, or other computers on your home network:
-
Open the user interface and the API to your network. Add this line to
.env:This opens exactly two ports: the user interface (8080) and the API (8000), which a Home Assistant host on your network talks to. The databases, Valkey and Ollama stay reachable from the server only, whatever you set here.
If the server has more than one network (a VPN, a second interface, a public address), set the server's home-network address instead of
0.0.0.0— for exampleKAMERPLANTER_BIND_ADDRESS=192.168.1.100— so the ports open on that network only.Anyone on your network can then use Kamerplanter
The release file runs in Light mode, which has no login. Once the ports are open, every device on your network can read and change your data. Only do this on a home network you trust, behind your router's firewall — never with port forwarding to the internet. Details: Light Mode.
-
Find your server's IP address:
-
On the other device, open a browser and go to
http://<IP-address>:8080 -
Update the CORS setting in
.envso the API accepts requests from the new address: -
Restart the services after the change:
Next steps¶
- Onboarding Wizard — Set up your first plants
- User Guide — All features in detail
- Kubernetes Deployment — For professional environments with high availability
Troubleshooting¶
Page won't load (connection refused)
Check if all services are running: docker compose -f docker-compose.release.yml -f docker-compose.override.yml ps. If the frontend service isn't running, check the logs: docker compose -f docker-compose.release.yml -f docker-compose.override.yml logs frontend.
Backend reports 'Connection refused' to the database
ArangoDB takes a bit longer on first start. Wait 30 seconds and check again. If the error persists: Do the passwords in .env match? ARANGO_ROOT_PASSWORD and ARANGODB_PASSWORD must be identical.
Can't access from another device
Check: (1) Is KAMERPLANTER_BIND_ADDRESS=0.0.0.0 set in .env, and did you restart afterwards? Without it, port 8080 is bound to 127.0.0.1 and refuses every other device. (2) Are both devices on the same network? (3) Is the IP address correct? (4) Is the CORS setting in .env updated? (5) Is a firewall blocking port 8080? Note the reverse, too: ports Docker publishes bypass host firewalls such as ufw or firewalld, so a firewall rule does not protect a port you opened with KAMERPLANTER_BIND_ADDRESS — the bind address is what does.
How much disk space does Kamerplanter need long-term?
The Docker images take about 2 GB. The database grows depending on usage — for a typical home user with up to 100 plants, data stays under 100 MB. Sensor data can grow faster when recording is enabled.