Posts for: #Docker

Using Tailscale Services

Up until a few days ago, I used the Docker sidecar method to expose services with easy to remember addresses. For instance, using it with Immich so I could just enter something like https://immich.gallant-panda.ts.net in the browser to get to it. This works well but always seemed a bit clunky since it requires an additional docker container to work.

With the beta release of Tailscale services there is, in my opinion, a better, cleaner way to do this. Services allow you to essentially expose a specific service (not the whole machine) as a node on your Tailnet. You can even assign tags to it in order to apply access control via the normal Tailscale ACL’s and grants. The service can also act as a sort of load balancer since you can host the service on multiple machines and Tailscale will connect the user to the closest one automatically.

[Read more]

Backup and Restore Docker Volume Data

Here are some scripts that can be used to manually backup and restore data from Docker volumes.

Manual Docker Volume Backup

This script uses a temporary Docker container to mount the target volume. It also mounts the current directory so it can write the archive file.

Usage: backup-docker-volume.sh <Volume Name> <Backup File Name>

#!/bin/bash

#!/bin/bash
# Usage: backup-docker-volume.sh <Volume Name> [Backup File Name]
# Example 1: ./backup-docker-volume.sh n8n_n8n_data n8n_data_backup
# Example 2: ./backup-docker-volume.sh n8n_n8n_data

# Use this command to find the name of the volume:
# docker inspect --format '{{ json .Mounts }}' <container_name_or_id>


# 1. Capture the volume name
VOLUME_NAME=$1

# 2. Check if the second argument ($2) is provided.
# If not, default to "n8n_backup_" + current timestamp.
TIMESTAMP=$(date +"%Y_%m_%d_%H_%M_%S")
BACKUP_NAME=${2:-"n8n_backup_$TIMESTAMP"}

# 3. Run the backup
docker run --rm \
  -v "$VOLUME_NAME":/source:ro \
  -v "$(pwd)":/backup \
  alpine tar czf /backup/"$BACKUP_NAME".tar.gz -C /source .

echo "Backup completed: ${BACKUP_NAME}.tar.gz"

Restore a Manual Backup

This script:

[Read more]

Restoring Offen Docker Volume Backups

This restores a backup from an Offen backup tar file.

Assumptions

  • The docker compose file is in a directory: /srv/docker/<project name>
  • The name of the Offen service in the compose file is: <project name>-backup

The Script

Usage: restore-backup.sh <folder in /srv/docker> <volume name> <backup file path>

Example: ./restore-backup.sh n8n n8n_n8n_data ../n8n/backup-2025-12-26T00-00-00.tar.gz

# Save the script dir for later when we need to reference the expanded backup files
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)

# Restore the specified backup to a temp directory
rm -rf ./tmp
mkdir tmp

tar -C ./tmp/ -xvf "$3"

# Create a backup of the current state
cd /srv/docker/$1
docker exec $1-backup backup
docker compose down

# Delete the volume contents
docker run --rm -v $2:/data/ alpine /bin/sh -c "rm -rf /data/*"

# Create a container to copy the files to the backup data to the Docker volume

# Map the volume to the backup_restore folder of the alpine container.
docker run -d --name temp_backup_restore -v $2:/backup_restore alpine

# Copy the backup contents to the backup_restore folder which then writes to the Docker volume.
docker cp $script_dir/tmp/backup/my-app-backup/. temp_backup_restore:/backup_restore

# Clean up
docker stop temp_backup_restore
docker rm temp_backup_restore

# Start containers
docker compose up -d

Note: I don’t know why Offen uses the “my-app-backup” folder in the backup path. I don’t set that anywhere in my Docker compose file or the Offen config file. This might be different for you.

[Read more]

Secure Your Docker Port Mappings

Secure Your Docker Port Mappings

When using Docker, a standard port mapping like 3000:3000 links the host machine’s port to the container’s port. This makes your application accessible to anyone on your network via the host’s IP (e.g., http://192.168.44.123:3000).

Restricting Access with a Reverse Proxy

If you use a reverse proxy to provide a clean URL like https://application.your-domain.com, you likely want to disable direct IP access for better security.

To prevent outside traffic from bypassing your proxy, restrict the port mapping to the local loopback address. Change your mapping from: 3000:3000 to: 127.0.0.1:3000:3000

[Read more]

Building Caddy Docker Images on a Raspberry Pi

The base Caddy Docker image is intentionally lean—around 50MB—to avoid bundling unnecessary components. However, if you need extra functionality, such as automatic SSL certificate issuance using the ACME DNS challenge via Cloudflare and Let’s Encrypt (as I did), you’ll need an image that includes the required plug-in. You have two options: use a community-maintained prebuilt image (the easy way), or build your own (the hard way). Here, we’re going with the hard way.

[Read more]