How can you backup your PostgreSQL to a Hetzner S3 object storage ?
Here is how it’s done.
Creating S3 Object Storage
First, let’s create our S3 Object Storage:
Next, we should create our S3 access key and secret:
Backup and restore
Let’s create a database with sample data:
-- Create the database
CREATE DATABASE music_streaming_platform;
\c music_streaming_platform;
-- Create users table
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
date_joined DATE DEFAULT CURRENT_DATE,
country VARCHAR(50)
);
-- Create artists table
CREATE TABLE artists (
artist_id SERIAL PRIMARY KEY,
artist_name VARCHAR(100) UNIQUE NOT NULL,
genre VARCHAR(50),
country VARCHAR(50),
active_since DATE
);
-- Create albums table
CREATE TABLE albums (
album_id SERIAL PRIMARY KEY,
album_name VARCHAR(100) NOT NULL,
release_date DATE,
artist_id INT REFERENCES artists(artist_id) ON DELETE CASCADE,
genre VARCHAR(50)
);
-- Create songs table
CREATE TABLE songs (
song_id SERIAL PRIMARY KEY,
song_name VARCHAR(100) NOT NULL,
duration TIME NOT NULL,
album_id INT REFERENCES albums(album_id) ON DELETE CASCADE,
plays INT DEFAULT 0,
release_date DATE
);
-- Create playlists table
CREATE TABLE playlists (
playlist_id SERIAL PRIMARY KEY,
playlist_name VARCHAR(100) NOT NULL,
user_id INT REFERENCES users(user_id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create playlist_songs table (many-to-many relationship between playlists and songs)
CREATE TABLE playlist_songs (
playlist_id INT REFERENCES playlists(playlist_id) ON DELETE CASCADE,
song_id INT REFERENCES songs(song_id) ON DELETE CASCADE,
PRIMARY KEY (playlist_id, song_id)
);
-- Insert data into users table
INSERT INTO users (username, email, country)
VALUES
('musiclover_101', 'lover101@example.com', 'USA'),
('rockfanatic', 'rockfan@example.com', 'UK'),
('classicbuff', 'classicbuff@example.com', 'Germany');
-- Insert data into artists table
INSERT INTO artists (artist_name, genre, country, active_since)
VALUES
('The Electric Beats', 'Rock', 'USA', '2005-06-15'),
('Synthwave Dream', 'Electronic', 'UK', '2012-04-20'),
('Classical Vibes', 'Classical', 'Germany', '1995-09-25');
-- Insert data into albums table
INSERT INTO albums (album_name, release_date, artist_id, genre)
VALUES
('Rock Revolution', '2018-07-22', 1, 'Rock'),
('Synthwave Sunrise', '2020-03-15', 2, 'Electronic'),
('Timeless Classics', '2000-11-12', 3, 'Classical');
-- Insert data into songs table
INSERT INTO songs (song_name, duration, album_id, plays, release_date)
VALUES
('Thunderstrike', '00:03:45', 1, 52340, '2018-07-22'),
('Neon Dreams', '00:04:12', 2, 78423, '2020-03-15'),
('Symphony No. 5', '00:07:30', 3, 152340, '2000-11-12'),
('Rock On', '00:03:55', 1, 62340, '2018-07-22'),
('Night Drive', '00:04:55', 2, 46450, '2020-03-15');
-- Insert data into playlists table
INSERT INTO playlists (playlist_name, user_id)
VALUES
('Morning Rock Hits', 1),
('Relaxing Classical', 3),
('Electronic Vibes', 2);
-- Insert data into playlist_songs table
INSERT INTO playlist_songs (playlist_id, song_id)
VALUES
(1, 1), -- Morning Rock Hits -> Thunderstrike
(1, 4), -- Morning Rock Hits -> Rock On
(2, 3), -- Relaxing Classical -> Symphony No. 5
(3, 2), -- Electronic Vibes -> Neon Dreams
(3, 5); -- Electronic Vibes -> Night Drive
-- Query to check all data
SELECT * FROM users;
SELECT * FROM artists;
SELECT * FROM albums;
SELECT * FROM songs;
SELECT * FROM playlists;
SELECT * FROM playlist_songs;
The database contains:
- users: Contains information about platform users (ID, username, email, date joined, country)
- artists: Stores information about artists (ID, name, genre, country, and when they started)
- albums: Holds album data (ID, name, release date, associated artist)
- songs: Stores song data (ID, name, duration, album, plays count, release date)
- playlists: Contains user-generated playlists (ID, name, user who created it, creation timestamp)
- playlist_songs: A many-to-many relationship between playlists and songs
Now let’s performs backups of a PostgreSQL database to an S3-compatible storage using eeshugerman/postgres-backup-s3
docker image:
docker run \
-e S3_REGION=US \
-e S3_ACCESS_KEY_ID=key \
-e S3_SECRET_ACCESS_KEY=secret \
-e S3_BUCKET=pg-bkp \
-e S3_PREFIX=backup \
-e POSTGRES_HOST=localhost \
-e POSTGRES_DATABASE=music_streaming_platform \
-e POSTGRES_USER=dummy \
-e POSTGRES_PASSWORD=123456 \
-e S3_ENDPOINT=https://fsn1.your-objectstorage.com \
--net=host \
eeshugerman/postgres-backup-s3:16
S3 Configuration needed :
- S3_REGION=US: Specifies the region where the S3 bucket is located (in this case, US).
- S3_ACCESS_KEY_ID=key: The access key ID used to authenticate with the S3-compatible storage service.
- S3_SECRET_ACCESS_KEY=secret: The secret access key used for authentication, paired with the access key ID.
- S3_BUCKET=pg-bkp: The name of the S3 bucket where backups will be stored.
- S3_PREFIX=backup: A folder-like prefix within the S3 bucket where backups will be saved. Think of it as a directory path.
- S3_ENDPOINT=https://fsn1.your-objectstorage.com: The endpoint URL for the S3-compatible object storage service. It’s used for non AWS S3 compatible storage
If everything it’s ok, then you’ll see:
Creating backup of music_streaming_platform database...
Uploading backup to pg-bkp-xxxx...
upload: ./db.dump to s3://pg-bkp-xxxx/backup/music_streaming_platform_2024-10-12T09:42:21.dump
Backup complete.
Now let’s drop the database and create an empty one:
DROP DATABASE music_streaming_platform;
CREATE DATABASE music_streaming_platform;
docker run \
-e S3_REGION=US \
-e S3_ACCESS_KEY_ID=key \
-e S3_SECRET_ACCESS_KEY=secret \
-e S3_BUCKET=pg-bkp \
-e S3_PREFIX=backup \
-e POSTGRES_HOST=localhost \
-e POSTGRES_DATABASE=music_streaming_platform \
-e POSTGRES_USER=dummy \
-e POSTGRES_PASSWORD=123456 \
-e S3_ENDPOINT=https://fsn1.your-objectstorage.com \
--net=host \
eeshugerman/postgres-backup-s3:16 \
sh restore.sh
If everything it’s ok, then you’ll see:
Finding latest backup...
Fetching backup from S3...
download: s3://pg-bkp-xxxx/backup/music_streaming_platform_2024-10-12T09:42:21.dump to ./db.dump
Restoring from backup...
Restore complete.
And in S3 object storage:
Now we can connect to Postgres and see our data:
sudo -u postgres psql
\c music_streaming_platform
You are now connected to database "music_streaming_platform" as user "postgres".
music_streaming_platform=# \dt
List of relations
Schema | Name | Type | Owner
--------+----------------+-------+----------
public | albums | table | postgres
public | artists | table | postgres
public | playlist_songs | table | postgres
public | playlists | table | postgres
public | songs | table | postgres
public | users | table | postgres
(6 rows)
music_streaming_platform=# select * from users;
user_id | username | email | date_joined | country
---------+----------------+-------------------------+-------------+---------
1 | musiclover_101 | lover101@example.com | 2024-10-12 | USA
2 | rockfanatic | rockfan@example.com | 2024-10-12 | UK
3 | classicbuff | classicbuff@example.com | 2024-10-12 | Germany
(3 rows)
Documentation and links:
- Docker images to periodically back up a PostgreSQL database to AWS S3 - https://github.com/eeshugerman/postgres-backup-s3