From the team behind aidrop.it — one workspace to build, host, and keep changing your code.

A host that keeps no backups is not lying to you. It is telling you where the copy has to come from: a job you run, writing to storage you own, on a schedule you chose. That is a smaller promise than "we back everything up nightly", and it has one advantage — you know exactly what exists, because you made it.

The backup is a job in your own project. It reads the database the same way your application does, writes the dump somewhere the host cannot delete, and reports when it last succeeded. Your coding agent can build and deploy it in one session, and once it runs the question "do we have a copy" has a timestamp for an answer.

Two ways to take a copy, by size

For a small database, an agent that can run SQL against it can copy it out table by table. For anything larger, the copy has to be produced next to the database, by pg_dump running inside the project, and shipped over HTTPS to a bucket you own. The first is an afternoon's answer; the second is a backup policy.

The line between them is the answer size, not the row count. A query verb that returns a page at a time is fine for a directory with a few thousand rows and the wrong tool for an orders table. When the agent starts paging through LIMIT/OFFSET to reconstruct a table, the job has become the better path.

Why the job runs inside the project

A managed database on a private network has no published port. Nothing outside the project can reach it, which is the point, and it also means your laptop cannot run pg_dump against it. The job runs where the connection string works: as one more service in the same project, attached to the instance the way the application is.

That gives it three properties for free. It reads the connection string from its environment, the same variable name the application uses. It leaves through the only door a container has, HTTPS on 443, which is enough for any object store. And it is a deployment like any other, so it has a log, and the log says why it stopped when it does.

What the job has to prove

A copy that was never restored is a file, not a backup. Keep the dump in a format pg_restore accepts, keep more than one, and rehearse the restore into an isolated database before you need it. Record the PostgreSQL version and the extensions the schema needs beside the dump; a restore that fails on a missing extension at 3am is the failure this whole exercise exists to prevent.

Prune on the bucket, not in the job. Lifecycle rules on your own storage keep the last thirty days without a line of code that can be wrong, and they keep working when the job does not. Then check the one number that matters, the age of the newest object, from something that is not the job itself. The question of whether you could actually leave has the same answer: the copy in your bucket is the thing you would take.

A pg_dump job, in one container

The job is a container that dumps on an interval and answers a health probe, since most hosts refuse a deployment that answers nothing on its port. It needs the database connection string and the bucket credentials in its environment, and nothing else. Adapt the schedule and the bucket; the shape is the same on any host.

FROM alpine:3.20
RUN apk add --no-cache postgresql16-client aws-cli busybox-extras
COPY backup.sh /backup.sh
CMD ["sh", "/backup.sh"]
# backup.sh — dump every INTERVAL seconds, keep answering /health on PORT
: "${DATABASE_URL:?}" "${BUCKET:?}" "${INTERVAL:=86400}" "${PORT:=8080}"
mkdir -p /tmp/www && echo ok > /tmp/www/health
httpd -p "$PORT" -h /tmp/www
while true; do
  f="/tmp/db-$(date -u +%Y%m%dT%H%M%SZ).dump"
  if pg_dump --format=custom --no-owner "$DATABASE_URL" > "$f"; then
    aws s3 cp "$f" "s3://$BUCKET/$(basename "$f")" && echo "backup ok $(basename "$f") $(stat -c %s "$f") bytes"
  else
    echo "backup FAILED: pg_dump exit $?"
  fi
  rm -f "$f"
  sleep "$INTERVAL"
done

Store DATABASE_URL, BUCKET, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as project secrets, never in the image. Then restore the newest dump into an empty database once, and write down how long it took.

FAQ

Is a host with no backups safe to use? Yes, if you can take a copy whenever you want one and you actually do. The risk is not the missing backup; it is assuming one exists. A host that says "none" has told you the truth; a job you run turns that into a copy you can name.

Can my coding agent set up database backups? Yes. The job above is a small repository, one deployment and four secrets. On aidrop.it the agent attaches the job to the Project's PostgreSQL instance with shared_resource_update, stores the bucket credentials with project_secret_set, and deploys it with service_build; for a small database it can also copy tables out directly with shared_database_query, one statement at a time.

How often should the job run? As often as the data you cannot afford to lose changes. A daily dump suits a site edited weekly; an orders table wants hours. Decide the number in minutes of lost work, then set the interval from that.

Where should the dump go? Object storage on an account the host does not control, with a lifecycle rule that keeps a fixed window. If the host's account and the bucket's account are the same, a billing problem can take both.

Before the next deploy

Get it running from the session you are already in

Connect your coding agent over MCP and ask it to deploy. aidrop builds the repository and runs it on a public address; when a build or the app fails, the log says why, and your agent fixes it and builds again.