← All guidesGitHub
Site Operations

Back Up a Docker Compose App and Test the Restore

Back up Docker Compose configuration, named volumes, and PostgreSQL to private object storage, then prove a restore works on an isolated server.

Jump to articleGitHub: TylorMayfield/docker-compose-spaces-recovery

Decide what must come back

A Docker Compose service is recoverable only when you can rebuild its configuration, restore the data it owns, and start it without guessing which secret or version it used. A Droplet backup or snapshot can be useful for a whole-machine problem. It does not replace a repeatable application restore test.

Start with one application. Write down its Compose directory, `.env` file, named volumes, bind mounts, image tags, database service, public hostname, and the harmless check that tells you it works. If you cannot name those things, you are not ready to automate a backup.

  • Copy configuration only from a protected directory. Never commit a production `.env` file to the companion repository.
  • Use a PostgreSQL logical dump for PostgreSQL data. Do not tar live database files from its volume.
  • Keep the decryption identity outside both the application server and the backup bucket.

Keep a small recovery inventory

Run these read-only commands in the application directory before changing anything. They show the services, the resolved Compose configuration, and the Docker volumes available on the server. Record the volume names that matter. A volume called `postgres_data` is common, but it is not a rule.

Inspect the deployed Compose applicationLocation shown per step
Replace every highlighted value before running this command.
cd /srv/YOUR_APP
sudo docker compose config --services
sudo docker compose config
sudo docker volume ls
sudo docker compose ps

Create private storage and separate keys

Create a private Spaces bucket for this recovery set. Enable versioning and set a lifecycle rule that matches the retention your team has agreed to. Use a full-access key only while you configure versioning or lifecycle rules. Give the scheduled backup job a limited key for this bucket. Create a second limited read key for the restore test, so the test machine never carries the writer credential.

Keep this bucket private. Do not add a CDN, a public policy, or a public listing because a backup is easier to browse that way. Those choices change the risk, not the convenience.

Run the backup once before scheduling it

The companion script makes a timestamped recovery set. It stops the running application services first and leaves the PostgreSQL service running for its custom-format dump. It captures the selected non-database volumes while those writers remain stopped, then resumes only the services it stopped. Pause external database and file writers for this entire window too. It encrypts the set before upload.

That short pause is deliberate. If your application cannot tolerate it, use the database and application vendor's documented online backup method. Do not quietly accept a backup that sometimes restores corrupted data.

Before these commands, install AWS CLI and age on the server. Docker and Compose must already run your application. Generate the age identity on your administrator workstation, retain the private key off the server, and put only its public recipient in AGE_RECIPIENT.

For a first rehearsal, use the companion’s fixture/ example on a disposable server. It creates a database record pointing to uploads/record-42.txt in a named app volume. Its README supplies the exact mapping and recovery check, so you do not need to invent an application.

Before continuing: Run the first backup by hand and inspect the resulting manifest before scheduling it. Keep the configuration file outside the cloned repository.

  1. Clone the companion projectConnected Droplet
    git clone https://github.com/TylorMayfield/docker-compose-spaces-recovery.git
  2. Open the project
    cd docker-compose-spaces-recovery
  3. Use the reviewed revision
    git checkout 08fcc40647eef74eb7015fb66c00a96386795dc0
  4. Create the protected configuration file
    sudo install -m 600 .env.example /etc/compose-recovery.env
  5. Set the backup valuesFile editor
    sudo nano /etc/compose-recovery.env
  6. Run the first backupConnected Droplet
    sudo ./scripts/backup-compose-app.sh /etc/compose-recovery.env

Check the recovery set before you trust it

The backup command should leave one manifest and the encrypted files under the same timestamp. List the remote prefix after the first run and save the timestamp in your operations notes. A missing manifest, a zero-byte archive, or a database dump without its matching configuration set is a failed backup.

Schedule only the command that you have run and inspected. Send its stderr to the alerting route your team already watches. Alert when the command fails or no complete backup arrives by its expected time.

Confirm the uploaded recovery setLocation shown per step
sudo bash -c 'set -a; . /etc/compose-recovery.env; set +a; export AWS_DEFAULT_REGION=us-east-1; aws s3 ls "s3://$BUCKET/$PREFIX/" --endpoint-url "https://$REGION.digitaloceanspaces.com"'

Restore on a different server

Use a fresh Linux Droplet, a test hostname, and a Cloud Firewall that exposes HTTPS only to the recovery administrator. Before the recovered application starts, block outbound traffic in the test firewall or at the host. This matters for chat servers, automations, payment callbacks, and any app that can send a message after it wakes up.

Download one complete timestamped set and its matching manifest using the read-only key. Decrypt it only on the test server. The companion creates new Docker volumes with a recovery prefix and restores their selected archives. If the set contains PostgreSQL, start a fresh test-only container and an empty test database first. In the root-only restore file, provide its container, database, user, and password; the script passes that password only to the restore process. The apply command writes to those new volumes and that test database, never production. Replace production hostnames in the recovered configuration with the test hostname before startup. Do not point a recovery test at the live domain.

Before continuing: The apply command writes recovered data into new recovery volumes and a test-only database. Run it only on the isolated test server after blocking outbound traffic and confirming RESTORE_ENV=test.

  1. Clone the companion projectIsolated test machine
    git clone https://github.com/TylorMayfield/docker-compose-spaces-recovery.git
  2. Open the project
    cd docker-compose-spaces-recovery
  3. Use the reviewed revision
    git checkout 08fcc40647eef74eb7015fb66c00a96386795dc0
  4. Create the restore configuration
    sudo install -m 600 .env.example /etc/compose-recovery-restore.env
  5. Set the test-only restore valuesFile editor
    sudo nano /etc/compose-recovery-restore.env
  6. Run the restore dry runIsolated test machine
    sudo ./scripts/restore-compose-app.sh /etc/compose-recovery-restore.env
  7. Apply on the test server
    sudo ./scripts/restore-compose-app.sh --apply /etc/compose-recovery-restore.env

Prove the application, then clean up

A green container list is not enough. Sign in to the test hostname, find one known record, and run the harmless health check you chose at the start. For a web app, that may mean opening a read-only page. For a worker, it may mean checking that the process starts while outbound work stays blocked.

Record the recovery-set timestamp, elapsed time, result, and anything you had to change. Rotate any credential that appeared outside its intended protected location. Destroy the test server or wipe the recovered data once the test ends.

Check your result

Expected result
The test server decrypts one complete set, starts the recovered application under a test hostname, and exposes one known read-only record without contacting production.
Stop if
Stop if the target is a production server, the backup set has a missing manifest or mismatched timestamp, the recovered service can send outbound traffic, a production hostname remains, or a secret appears in output.
Next step
Record the timestamp, recovery duration, and result. Remove recovered data from the test server and investigate every manual change before scheduling the job.