mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-18 03:57:17 +00:00
183 lines
5.8 KiB
Markdown
183 lines
5.8 KiB
Markdown
# 2. Docker (single container)
|
|
|
|
One Puter container. You bring your own database, S3, etc. — or run with the bundled in-process defaults for a quick spin. The image is multi-arch (`linux/amd64`, `linux/arm64`).
|
|
|
|
## Requirements
|
|
|
|
- **Docker** (any recent version).
|
|
|
|
## Bare minimum — defaults, single command
|
|
|
|
```bash
|
|
mkdir -p puter/config puter/data
|
|
|
|
docker run -d \
|
|
--name puter \
|
|
--restart unless-stopped \
|
|
-p 4100:4100 \
|
|
-v $(pwd)/puter/config:/etc/puter \
|
|
-v $(pwd)/puter/data:/var/puter \
|
|
ghcr.io/heyputer/puter:latest
|
|
```
|
|
|
|
Open <http://puter.localhost:4100>. With nothing in `puter/config/`, the in-process defaults kick in (same SQLite + dynalite + fauxqs + redis-mock as dev mode). State lands in `puter/data/`. Login is `admin` — temp password is printed once in `docker logs puter`.
|
|
|
|
That's enough to confirm the image works. Now configure for real.
|
|
|
|
## Add a config
|
|
|
|
The container reads **`/etc/puter/config.json`** and deep-merges it on top of the bundled defaults. You only put the keys you want to change.
|
|
|
|
```bash
|
|
cat > puter/config/config.json <<'JSON'
|
|
{
|
|
"domain": "puter.example.com",
|
|
"protocol": "https",
|
|
"pub_port": 443,
|
|
"env": "prod",
|
|
|
|
"jwt_secret": "REPLACE-WITH-openssl-rand-hex-64",
|
|
"url_signature_secret": "REPLACE-WITH-A-DIFFERENT-openssl-rand-hex-64"
|
|
}
|
|
JSON
|
|
|
|
docker restart puter
|
|
```
|
|
|
|
> 🔒 **Always replace `jwt_secret` and `url_signature_secret`.** The defaults are baked into the public image. Generate with `openssl rand -hex 64`.
|
|
|
|
Watch the logs:
|
|
|
|
```bash
|
|
docker logs -f puter
|
|
```
|
|
|
|
Look for `[config] override from /etc/puter/config.json` — that's the success signal.
|
|
|
|
## Wire to external services
|
|
|
|
Drop the relevant block(s) into `config.json`. Mix and match. Restart with `docker restart puter` after any change.
|
|
|
|
### MySQL / MariaDB
|
|
|
|
Puter applies its schema on first boot when you set `migrationPaths`:
|
|
|
|
```json
|
|
{
|
|
"database": {
|
|
"engine": "mysql",
|
|
"host": "db.internal", "port": 3306,
|
|
"user": "puter", "password": "...", "database": "puter",
|
|
"migrationPaths": ["/opt/puter/dist/src/backend/clients/database/migrations/mysql"]
|
|
}
|
|
}
|
|
```
|
|
|
|
Two files run in order: `mysql_mig_1.sql` (tables) and `mysql_mig_2.sql` (default apps — editor, viewer, pdf, camera, player, recorder, git, dev-center, puter-linux). Both are idempotent — safe to re-run.
|
|
|
|
### S3 (real or S3-compatible)
|
|
|
|
```json
|
|
{
|
|
"s3": {
|
|
"s3Config": {
|
|
"endpoint": "https://s3.example.com",
|
|
"accessKeyId": "...", "secretAccessKey": "...",
|
|
"region": "us-east-1"
|
|
}
|
|
},
|
|
"s3_bucket": "my-puter-bucket",
|
|
"s3_region": "us-east-1"
|
|
}
|
|
```
|
|
|
|
The bucket must exist already — Puter doesn't create it.
|
|
|
|
For real AWS S3 the example above works as-is — virtual-hosted DNS form (`<bucket>.s3.amazonaws.com`) is the AWS SDK default. For S3-compatible servers (RustFS, MinIO, fauxqs), add `"forcePathStyle": true` inside the `s3Config` block — virtual-hosted DNS doesn't resolve there.
|
|
|
|
> ⚠️ **S3 uses camelCase keys** (`accessKeyId` / `secretAccessKey`). DynamoDB below uses snake_case. They're not the same.
|
|
|
|
### DynamoDB (real AWS)
|
|
|
|
Provision the table externally (e.g. Terraform):
|
|
|
|
```json
|
|
{
|
|
"dynamo": {
|
|
"aws": { "access_key": "...", "secret_key": "...", "region": "us-east-1" }
|
|
}
|
|
}
|
|
```
|
|
|
|
The KV table is named `store-kv-v1`. Schema: hash `namespace` (S), range `key` (S), LSI `lsi1-index` on `lsi1` (S), TTL on `ttl`.
|
|
|
|
### Redis / Valkey cluster
|
|
|
|
Puter speaks ioredis cluster protocol. Real Redis cluster:
|
|
|
|
```json
|
|
{ "redis": { "startupNodes": [{ "host": "redis-0", "port": 6379 }] } }
|
|
```
|
|
|
|
For a self-hosted single Valkey/Redis container, run it in cluster mode (one node, all 16384 slots assigned to itself) and turn off TLS:
|
|
|
|
```json
|
|
{
|
|
"redis": {
|
|
"startupNodes": [{ "host": "valkey", "port": 6379 }],
|
|
"tls": false
|
|
}
|
|
}
|
|
```
|
|
|
|
TLS defaults to on (matches the prod ElastiCache shape) — set `false` for plain-TCP self-host.
|
|
|
|
## What persists?
|
|
|
|
Anything your config points at `/var/puter/...` lives on the host via the `puter/data` mount (SQLite path, fauxqs data dirs if you use them, etc.). If you've moved every dependency to external services, the data volume is mostly empty and optional.
|
|
|
|
## Updating
|
|
|
|
```bash
|
|
docker pull ghcr.io/heyputer/puter:latest
|
|
docker rm -f puter
|
|
# re-run the docker run command above
|
|
```
|
|
|
|
Your `config.json` and persistent data are untouched.
|
|
|
|
## Building the image yourself
|
|
|
|
```bash
|
|
docker build -t puter .
|
|
|
|
# Multi-arch (requires buildx, on by default in modern Docker):
|
|
docker buildx build \
|
|
--platform linux/amd64,linux/arm64 \
|
|
-t your-registry/puter:latest \
|
|
--push .
|
|
```
|
|
|
|
A `docker-compose.yml` in this directory has a commented-out `build:` block — uncomment it (and flip `pull_policy` to `never`) to build from your local checkout instead of pulling.
|
|
|
|
## Troubleshooting
|
|
|
|
**`docker logs puter` shows the container restarting.**
|
|
Almost always JSON syntax in `config.json`. Validate: `jq . puter/config/config.json`.
|
|
|
|
**The config file isn't picked up.**
|
|
Confirm it resolves to `/etc/puter/config.json` *inside* the container:
|
|
```bash
|
|
docker exec puter cat /etc/puter/config.json
|
|
```
|
|
Empty / missing → the volume mount path is wrong.
|
|
|
|
**Healthcheck reports unhealthy but the site works.**
|
|
The healthcheck hits `puter.localhost:4100/test` from inside the container. If you changed `domain` or `port`, the check still uses defaults. The site itself is fine.
|
|
|
|
**`Error: DynamoDB aws config requires both access_key and secret_key`.**
|
|
You wrote `accessKeyId` / `secretAccessKey` (the AWS SDK form) under `dynamo.aws`. DynamoDB config uses snake_case. See above.
|
|
|
|
**Architecture mismatch on Apple Silicon / ARM hosts.**
|
|
Use the published `:latest` tag — it's already multi-arch. If you built locally with `docker build` on an Intel Mac, the resulting image will be `linux/amd64` only.
|