<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>pixelfed &amp;mdash; roblog</title>
    <link>https://blog.rstokes.uk/tag:pixelfed</link>
    <description>Random musings—mostly for me, but you might accidentally find something useful too</description>
    <pubDate>Sat, 25 Jul 2026 23:31:59 +0100</pubDate>
    <item>
      <title>Installing Pixelfed Glitch on a Raspberry Pi</title>
      <link>https://blog.rstokes.uk/installing-pixelfed-glitch-on-a-raspberry-pi</link>
      <description>&lt;![CDATA[Although the official Pixelfed Glitch Docker docs are fairly short, I found a few rough edges when installing it on a Raspberry Pi. This guide pulls the official instructions together with the fixes I needed along the way.&#xA;&#xA;This uses the Docker version of Pixelfed Glitch, with MariaDB and Redis running as Docker containers.&#xA;&#xA;!--more--&#xA;&#xA;div style=&#34;margin-top:-20px&#34;/div&#xA;&#xA;Contents&#xA;&#xA;a href=&#34;#install-docker&#34;Step 1 – Install Docker/a&#xA;a href=&#34;#allow-your-user-to-run-docker&#34;Step 2 – Allow your user to run Docker/a&#xA;a href=&#34;#create-the-pixelfed-glitch-directory&#34;Step 3 – Create the Pixelfed Glitch directory/a&#xA;a href=&#34;#download-the-docker-files&#34;Step 4 – Download the Docker files/a&#xA;a href=&#34;#edit-the-environment-file&#34;Step 5 – Edit the environment file/a&#xA;a href=&#34;#prepare-pixelfeds-storage-directories&#34;Step 6 – Prepare Pixelfed&#39;s storage directories/a&#xA;a href=&#34;#start-pixelfed-glitch&#34;Step 7 – Start Pixelfed Glitch/a&#xA;a href=&#34;#generate-the-application-and-oauth-keys&#34;Step 8 – Generate the application and OAuth keys/a&#xA;a href=&#34;#stop-first-time-setup-from-running-again&#34;Step 9 – Stop first-time setup from running again/a&#xA;a href=&#34;#check-the-site&#34;Step 10 – Check the site/a&#xA;a href=&#34;#create-the-first-user&#34;Step 11 – Create the first user/a&#xA;a href=&#34;#create-another-user&#34;Step 12 – Create another user/a&#xA;a href=&#34;#useful-checks&#34;Step 13 – Useful checks/a&#xA;a href=&#34;#mobile-app-login-and-2fa&#34;Step 14 – Mobile app login and 2FA/a&#xA;a href=&#34;#reboot-behaviour&#34;Step 15 – Reboot behaviour/a&#xA;a href=&#34;#backups&#34;Step 16 – Backups/a&#xA;a href=&#34;#troubleshooting-notes&#34;Troubleshooting notes/a&#xA;&#xA;div id=&#34;install-docker&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 1. Install Docker&#xA;&#xA;Pixelfed Glitch will run inside Docker containers. This means we do not need to manually install PHP, MariaDB, Redis, Node, Composer, or most of the other application dependencies on the Raspberry Pi itself.&#xA;&#xA;i. Update the package list:&#xA;&#xA;presudo apt update &amp;&amp; sudo apt upgrade/pre&#xA;&#xA;ii. Install the packages needed to add Docker&#39;s repository:&#xA;&#xA;presudo apt install -y ca-certificates curl/pre&#xA;&#xA;iii. Create the keyrings directory:&#xA;&#xA;presudo install -m 0755 -d /etc/apt/keyrings/pre&#xA;&#xA;iv. Download Docker&#39;s GPG key:&#xA;&#xA;presudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc/pre&#xA;&#xA;v. Make the key readable:&#xA;&#xA;presudo chmod a+r /etc/apt/keyrings/docker.asc/pre&#xA;&#xA;vi. Add Docker&#39;s repository:&#xA;&#xA;presudo tee /etc/apt/sources.list.d/docker.sources &amp;lt;&amp;lt;EOF&#xA;Types: deb&#xA;URIs: https://download.docker.com/linux/debian&#xA;Suites: $(. /etc/os-release &amp;&amp; echo &#34;$VERSIONCODENAME&#34;)&#xA;Components: stable&#xA;Architectures: $(dpkg --print-architecture)&#xA;Signed-By: /etc/apt/keyrings/docker.asc&#xA;EOF/pre&#xA;&#xA;vii. Update the package list again:&#xA;&#xA;presudo apt update/pre&#xA;&#xA;viii. Install Docker and the Compose plugin:&#xA;&#xA;presudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin/pre&#xA;&#xA;ix. Check Docker is running:&#xA;&#xA;presudo systemctl status docker/pre&#xA;&#xA;x. Enable Docker to start at boot:&#xA;&#xA;presudo systemctl enable docker.service&#xA;sudo systemctl enable containerd.service/pre&#xA;&#xA;div id=&#34;allow-your-user-to-run-docker&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 2. Allow your user to run Docker&#xA;&#xA;By default, Docker commands need sudo. To run docker compose as your normal user, add your user to the docker group.&#xA;&#xA;i. Create the group, if it does not already exist:&#xA;&#xA;presudo groupadd docker 2  /dev/null || true/pre&#xA;&#xA;ii. Add your user to it:&#xA;&#xA;presudo usermod -aG docker $USER/pre&#xA;&#xA;iii. Log out of SSH and log back in, or reboot the Pi.&#xA;&#xA;iv. Test Docker without sudo:&#xA;&#xA;predocker run hello-world/pre&#xA;&#xA;If this works, Docker is ready.&#xA;&#xA;div id=&#34;create-the-pixelfed-glitch-directory&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 3. Create the Pixelfed Glitch directory&#xA;&#xA;I installed Pixelfed Glitch under my home directory:&#xA;&#xA;precd /home/pi&#xA;mkdir -p pixelfed-glitch&#xA;cd pixelfed-glitch/pre&#xA;&#xA;Change /home/pi to match your own username if needed.&#xA;&#xA;div id=&#34;download-the-docker-files&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 4. Download the Docker files&#xA;&#xA;Download the Docker Compose file and the example environment file from the Pixelfed Glitch repository:&#xA;&#xA;precurl -fsSL https://raw.githubusercontent.com/pixelfed-glitch/pixelfed/develop/docker-compose.yml -o docker-compose.yml&#xA;curl -fsSL https://raw.githubusercontent.com/pixelfed-glitch/pixelfed/develop/.env.docker -o .env/pre&#xA;&#xA;The docker-compose.yml file defines the containers. The .env file holds site-specific settings such as the domain name, database password, upload limits, email settings, and Docker image tags.&#xA;&#xA;div id=&#34;edit-the-environment-file&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 5. Edit the environment file&#xA;&#xA;Open the .env file:&#xA;&#xA;prenano .env/pre&#xA;&#xA;At minimum, change the following values.&#xA;&#xA;Set the name of the site:&#xA;&#xA;preAPPNAME=&#34;My Pixelfed Site&#34;/pre&#xA;&#xA;Set the domain name, without https:// and without a trailing slash:&#xA;&#xA;preAPPDOMAIN=&#34;photos.example.com&#34;/pre&#xA;&#xA;Leave APPURL like this:&#xA;&#xA;preAPPURL=&#34;https://${APPDOMAIN}&#34;/pre&#xA;&#xA;Leave ADMINDOMAIN like this:&#xA;&#xA;preADMINDOMAIN=&#34;${APPDOMAIN}&#34;/pre&#xA;&#xA;Set a public contact email address:&#xA;&#xA;preINSTANCECONTACTEMAIL=&#34;admin@example.com&#34;/pre&#xA;&#xA;Set a secure database password:&#xA;&#xA;preDBPASSWORD=&#34;CHANGETHISTOALONGRANDOMPASSWORD&#34;/pre&#xA;&#xA;If you do not plan to send email at first, you can disable mandatory email verification:&#xA;&#xA;preENFORCEEMAILVERIFICATION=&#34;false&#34;/pre&#xA;&#xA;Make sure OAuth is enabled, as this is needed for Pixelfed mobile apps:&#xA;&#xA;preOAUTHENABLED=&#34;true&#34;/pre&#xA;&#xA;Leave APPKEY empty at this stage:&#xA;&#xA;preAPPKEY=/pre&#xA;&#xA;Pixelfed will generate this later. If you ever set it manually, it must be one single Laravel key, for example:&#xA;&#xA;preAPPKEY=base64:CHANGETHISTOONEVALIDKEYONLY/pre&#xA;&#xA;Do not paste two keys together on the same line.&#xA;&#xA;div id=&#34;prepare-pixelfeds-storage-directories&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 6. Prepare Pixelfed&#39;s storage directories&#xA;&#xA;The Docker image expects several Laravel storage paths to exist. Creating them now avoids a startup error such as:&#xA;&#xA;prePlease provide a valid cache path./pre&#xA;&#xA;Run:&#xA;&#xA;precd /home/pi/pixelfed-glitch&#xA;&#xA;sudo mkdir -p docker-compose-state/data/pixelfed/storage/app/public&#xA;sudo mkdir -p docker-compose-state/data/pixelfed/storage/framework/cache/data&#xA;sudo mkdir -p docker-compose-state/data/pixelfed/storage/framework/sessions&#xA;sudo mkdir -p docker-compose-state/data/pixelfed/storage/framework/testing&#xA;sudo mkdir -p docker-compose-state/data/pixelfed/storage/framework/views&#xA;sudo mkdir -p docker-compose-state/data/pixelfed/storage/logs&#xA;sudo mkdir -p docker-compose-state/data/pixelfed/storage/docker/lock&#xA;sudo mkdir -p docker-compose-state/data/pixelfed/cache&#xA;&#xA;sudo chown -R 33:33 docker-compose-state/data/pixelfed/storage&#xA;sudo chown -R 33:33 docker-compose-state/data/pixelfed/cache/pre&#xA;&#xA;User ID 33 is www-data, which is the user Pixelfed runs as inside the container.&#xA;&#xA;div id=&#34;start-pixelfed-glitch&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 7. Start Pixelfed Glitch&#xA;&#xA;Start the containers:&#xA;&#xA;predocker compose up -d/pre&#xA;&#xA;This downloads the Pixelfed Glitch, MariaDB, and Redis images, creates the containers, and starts the first-time setup.&#xA;&#xA;Check the containers:&#xA;&#xA;predocker compose ps/pre&#xA;&#xA;You should eventually see web, worker, cron, db, and redis as Up.&#xA;&#xA;To watch the logs:&#xA;&#xA;predocker compose logs --tail=100 --follow/pre&#xA;&#xA;If you only want the web container logs:&#xA;&#xA;predocker compose logs --tail=100 web/pre&#xA;&#xA;div id=&#34;generate-the-application-and-oauth-keys&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 8. Generate the application and OAuth keys&#xA;&#xA;If the site shows a 500 error and the logs mention either of these:&#xA;&#xA;preNo application encryption key has been specified.&#xA;Invalid key supplied/pre&#xA;&#xA;run the following commands:&#xA;&#xA;predocker compose exec web php artisan key:generate --force&#xA;docker compose exec -u www-data web php artisan passport:keys --force/pre&#xA;&#xA;Then clear and rebuild Pixelfed&#39;s cached config:&#xA;&#xA;predocker compose exec -u www-data web php artisan view:clear&#xA;docker compose exec -u www-data web php artisan config:clear&#xA;docker compose exec -u www-data web php artisan cache:clear&#xA;docker compose exec -u www-data web php artisan config:cache/pre&#xA;&#xA;Restart the Pixelfed containers:&#xA;&#xA;predocker compose restart web worker cron/pre&#xA;&#xA;div id=&#34;stop-first-time-setup-from-running-again&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 9. Stop first-time setup from running again&#xA;&#xA;After the first-time setup has completed and the keys have been created, edit .env again:&#xA;&#xA;prenano .env/pre&#xA;&#xA;Add this line:&#xA;&#xA;preDOCKERAPPRUNONETIMESETUPTASKS=&#34;0&#34;/pre&#xA;&#xA;This stops Pixelfed from trying to recreate encryption keys on every container start.&#xA;&#xA;If this is not set, the container may restart repeatedly with an error like:&#xA;&#xA;preEncryption keys already exist. Use the --force option to overwrite them./pre&#xA;&#xA;Restart the stack:&#xA;&#xA;predocker compose down&#xA;docker compose up -d/pre&#xA;&#xA;Do not use docker compose down -v, as that removes Docker volumes.&#xA;&#xA;div id=&#34;check-the-site&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 10. Check the site&#xA;&#xA;Open your Pixelfed URL in a browser:&#xA;&#xA;prehttps://photos.example.com/pre&#xA;&#xA;If you are using NGINX as a reverse proxy, make sure it points to the Docker web port. In the default Pixelfed Glitch Docker file this is:&#xA;&#xA;pre8080/pre&#xA;&#xA;So the proxy target is:&#xA;&#xA;prehttp://127.0.0.1:8080/pre&#xA;&#xA;Check the running containers:&#xA;&#xA;predocker compose ps/pre&#xA;&#xA;If you see a 502 error, the web container is probably not running yet, or it is restarting. Check:&#xA;&#xA;predocker compose logs --tail=200 web/pre&#xA;&#xA;div id=&#34;create-the-first-user&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 11. Create the first user&#xA;&#xA;Create the first account:&#xA;&#xA;predocker compose exec -u www-data web php artisan user:create/pre&#xA;&#xA;Follow the prompts.&#xA;&#xA;To make the user an admin:&#xA;&#xA;predocker compose exec -u www-data web php artisan user:admin USERNAME/pre&#xA;&#xA;To manually verify an email address:&#xA;&#xA;predocker compose exec -u www-data web php artisan user:verifyemail USERNAME/pre&#xA;&#xA;For example:&#xA;&#xA;predocker compose exec -u www-data web php artisan user:admin rob&#xA;docker compose exec -u www-data web php artisan user:verifyemail rob/pre&#xA;&#xA;div id=&#34;create-another-user&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 12. Create another user&#xA;&#xA;Run the same create command again:&#xA;&#xA;predocker compose exec -u www-data web php artisan user:create/pre&#xA;&#xA;Follow the prompts and enter the new username.&#xA;&#xA;For example:&#xA;&#xA;prebnw/pre&#xA;&#xA;Only run the admin command for accounts that should have admin rights.&#xA;&#xA;div id=&#34;useful-checks&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 13. Useful checks&#xA;&#xA;Check container status:&#xA;&#xA;predocker compose ps/pre&#xA;&#xA;Show recent logs:&#xA;&#xA;predocker compose logs --tail=200 web/pre&#xA;&#xA;Check the current Pixelfed config values:&#xA;&#xA;predocker compose exec -u www-data web php artisan tinker --execute=&#39;&#xA;echo &#34;app.url=&#34;.config(&#34;app.url&#34;).PHPEOL;&#xA;echo &#34;oauth=&#34;.(config(&#34;pixelfed.oauthenabled&#34;) ? &#34;true&#34; : &#34;false&#34;).PHPEOL;&#xA;echo &#34;session.domain=&#34;.config(&#34;session.domain&#34;).PHPEOL;&#xA;&#39;/pre&#xA;&#xA;Check the application key:&#xA;&#xA;pregrep &#39;^APPKEY=&#39; .env/pre&#xA;&#xA;A valid key should look like one single line:&#xA;&#xA;preAPPKEY=base64:LONGRANDOMSTRINGHERE/pre&#xA;&#xA;Check the OAuth key files:&#xA;&#xA;predocker compose exec -u www-data web ls -l storage/oauth-private.key storage/oauth-public.key/pre&#xA;&#xA;div id=&#34;mobile-app-login-and-2fa&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 14. Mobile app login and 2FA&#xA;&#xA;The web version worked fine for me with 2FA enabled.&#xA;&#xA;The mobile app login flow was different. With 2FA enabled, the official Pixelfed app and Impressia both opened a web login pop-up, accepted the username, password, and 2FA code, then stayed in the web version inside the pop-up instead of returning to the app.&#xA;&#xA;The workaround was:&#xA;&#xA;Keep 2FA enabled on the admin account.&#xA;Use a separate non-admin account for mobile app login.&#xA;Disable 2FA on that non-admin account before logging in to the apps.&#xA;&#xA;If the web interface will not disable 2FA and clicking the green &#34;Enabled&#34; button only adds # to the URL, disable it from the database.&#xA;&#xA;First check the 2FA column names:&#xA;&#xA;predocker compose exec -u www-data web php artisan tinker --execute=&#39;&#xA;printr(arrayvalues(arrayfilter(&#xA;&#x9;Schema::getColumnListing(&#34;users&#34;),&#xA;&#x9;fn($c) =  strcontains(strtolower($c), &#34;two&#34;) || strcontains(strtolower($c), &#34;2fa&#34;) || strcontains(strtolower($c), &#34;otp&#34;)&#xA;)));&#xA;&#39;/pre&#xA;&#xA;On my install the columns were:&#xA;&#xA;pre2faenabled&#xA;2fasecret&#xA;2fabackupcodes&#xA;2fasetupat/pre&#xA;&#xA;To disable 2FA for a user called bnw:&#xA;&#xA;predocker compose exec -u www-data web php artisan tinker --execute=&#39;&#xA;DB::table(&#34;users&#34;)-  where(&#34;username&#34;, &#34;bnw&#34;)-  update([&#xA;&#x9;&#34;2faenabled&#34; =  false,&#xA;&#x9;&#34;2fasecret&#34; =  null,&#xA;&#x9;&#34;2fabackupcodes&#34; =  null,&#xA;&#x9;&#34;2fasetupat&#34; =  null,&#xA;]);&#xA;echo &#34;2FA disabled for bnw\n&#34;;&#xA;&#39;/pre&#xA;&#xA;After that, app login worked.&#xA;&#xA;div id=&#34;reboot-behaviour&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 15. Reboot behaviour&#xA;&#xA;The docker-compose.yml file uses:&#xA;&#xA;prerestart: unless-stopped/pre&#xA;&#xA;This means the containers should come back up after a reboot, as long as Docker itself starts on boot.&#xA;&#xA;Check Docker is enabled:&#xA;&#xA;presystemctl is-enabled docker/pre&#xA;&#xA;If needed:&#xA;&#xA;presudo systemctl enable docker/pre&#xA;&#xA;After a reboot, check Pixelfed:&#xA;&#xA;precd /home/pi/pixelfed-glitch&#xA;docker compose ps/pre&#xA;&#xA;One thing to remember: if you run this command:&#xA;&#xA;predocker compose down/pre&#xA;&#xA;the containers are removed. Docker cannot restart removed containers at boot. To recreate and start them:&#xA;&#xA;precd /home/pi/pixelfed-glitch&#xA;docker compose up -d/pre&#xA;&#xA;div id=&#34;backups&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Step 16. Backups&#xA;&#xA;At minimum, back up the Pixelfed Glitch install directory:&#xA;&#xA;pre/home/pi/pixelfed-glitch/pre&#xA;&#xA;This contains:&#xA;&#xA;predocker-compose.yml&#xA;.env&#xA;docker-compose-state//pre&#xA;&#xA;The .env file contains secrets, including the database password and application key, so store backups securely.&#xA;&#xA;The docker-compose-state directory contains the MariaDB data, Redis data, Pixelfed storage, and cache folders. For a live database, a proper database dump is safer than only copying files while containers are running.&#xA;&#xA;A simple manual MariaDB dump can be created with:&#xA;&#xA;precd /home/pi/pixelfed-glitch&#xA;docker compose exec db mariadb-dump -u pixelfed -p pixelfedprod   pixelfeddbbackup.sql/pre&#xA;&#xA;You will be prompted for the database password from .env.&#xA;&#xA;div id=&#34;troubleshooting-notes&#34; style=&#34;margin-top:-15px&#34;/div&#xA;&#xA;Troubleshooting notes&#xA;&#xA;a href=&#34;#docker-permission-denied&#34;Docker permission denied/a&#xA;a href=&#34;#missing-cache-path&#34;Missing cache path/a&#xA;a href=&#34;#invalid-application-key&#34;Invalid application key/a&#xA;a href=&#34;#first-time-setup-keeps-running&#34;First-time setup keeps running/a&#xA;&#xA;div id=&#34;docker-permission-denied&#34; style=&#34;margin-top:-5px&#34;/div&#xA;&#xA;Docker permission denied&#xA;&#xA;If you see:&#xA;&#xA;prepermission denied while trying to connect to the docker API at unix:///var/run/docker.sock/pre&#xA;&#xA;your user is not in the docker group, or the current login session has not picked up the new group membership.&#xA;&#xA;Fix:&#xA;&#xA;presudo usermod -aG docker $USER/pre&#xA;&#xA;Then log out and back in.&#xA;&#xA;div id=&#34;missing-cache-path&#34; style=&#34;margin-top:-5px&#34;/div&#xA;&#xA;Missing cache path&#xA;&#xA;If the web container restarts and logs show:&#xA;&#xA;prePlease provide a valid cache path./pre&#xA;&#xA;create the Laravel storage and cache folders from Step 6.&#xA;&#xA;div id=&#34;invalid-application-key&#34; style=&#34;margin-top:-5px&#34;/div&#xA;&#xA;Invalid application key&#xA;&#xA;If the site shows a 500 error and logs show:&#xA;&#xA;preUnsupported cipher or incorrect key length/pre&#xA;&#xA;check .env:&#xA;&#xA;pregrep &#39;^APPKEY=&#39; .env/pre&#xA;&#xA;The key must be one single key. It must not look like this:&#xA;&#xA;preAPPKEY=base64:KEYONE=base64:KEYTWO/pre&#xA;&#xA;To replace it on a new install:&#xA;&#xA;prenewkey=&#34;base64:$(openssl rand -base64 32)&#34;&#xA;sudo sed -i.bak &#34;s|^APPKEY=.*|APPKEY=${newkey}|&#34; .env/pre&#xA;&#xA;Then clear the caches:&#xA;&#xA;predocker compose exec -u www-data web php artisan view:clear&#xA;docker compose exec -u www-data web php artisan config:clear&#xA;docker compose exec -u www-data web php artisan cache:clear&#xA;docker compose exec -u www-data web php artisan config:cache&#xA;docker compose restart web worker cron/pre&#xA;&#xA;div id=&#34;first-time-setup-keeps-running&#34; style=&#34;margin-top:-5px&#34;/div&#xA;&#xA;First-time setup keeps running&#xA;&#xA;If the web container keeps restarting and logs show:&#xA;&#xA;preEncryption keys already exist. Use the --force option to overwrite them./pre&#xA;&#xA;set this in .env:&#xA;&#xA;preDOCKERAPPRUNONETIMESETUPTASKS=&#34;0&#34;/pre&#xA;&#xA;Then restart:&#xA;&#xA;predocker compose down&#xA;docker compose up -d/pre&#xA;&#xA;div style=&#34;margin-top:-10px&#34;/div&#xA;&#xA;span style=&#34;font-size:0.95em;font-weight:bold;&#34;Sources:/span&#xA;span style=&#34;font-size:0.85em;&#34;Pixelfed Glitch Docker installation docs&#xA;Pixelfed Glitch configuration docs&#xA;Docker Engine Debian installation docs&#xA;Docker Linux post-installation docs&#xA;/span&#xA;&#xA;brTags: #Pixelfed #RaspberryPi]]&gt;</description>
      <content:encoded><![CDATA[<p>Although the official Pixelfed Glitch Docker docs are fairly short, I found a few rough edges when installing it on a Raspberry Pi. This guide pulls the official instructions together with the fixes I needed along the way.</p>

<p>This uses the Docker version of Pixelfed Glitch, with MariaDB and Redis running as Docker containers.</p>



<div style="margin-top:-20px"></div>

<h3 id="contents">Contents</h3>

<p><a href="#install-docker">Step 1 – Install Docker</a>
<a href="#allow-your-user-to-run-docker">Step 2 – Allow your user to run Docker</a>
<a href="#create-the-pixelfed-glitch-directory">Step 3 – Create the Pixelfed Glitch directory</a>
<a href="#download-the-docker-files">Step 4 – Download the Docker files</a>
<a href="#edit-the-environment-file">Step 5 – Edit the environment file</a>
<a href="#prepare-pixelfeds-storage-directories">Step 6 – Prepare Pixelfed&#39;s storage directories</a>
<a href="#start-pixelfed-glitch">Step 7 – Start Pixelfed Glitch</a>
<a href="#generate-the-application-and-oauth-keys">Step 8 – Generate the application and OAuth keys</a>
<a href="#stop-first-time-setup-from-running-again">Step 9 – Stop first-time setup from running again</a>
<a href="#check-the-site">Step 10 – Check the site</a>
<a href="#create-the-first-user">Step 11 – Create the first user</a>
<a href="#create-another-user">Step 12 – Create another user</a>
<a href="#useful-checks">Step 13 – Useful checks</a>
<a href="#mobile-app-login-and-2fa">Step 14 – Mobile app login and 2FA</a>
<a href="#reboot-behaviour">Step 15 – Reboot behaviour</a>
<a href="#backups">Step 16 – Backups</a>
<a href="#troubleshooting-notes">Troubleshooting notes</a></p>

<div id="install-docker" style="margin-top:-15px"></div>

<h3 id="step-1-install-docker">Step 1. Install Docker</h3>

<p>Pixelfed Glitch will run inside Docker containers. This means we do not need to manually install PHP, MariaDB, Redis, Node, Composer, or most of the other application dependencies on the Raspberry Pi itself.</p>

<p>i. Update the package list:</p>

<pre>sudo apt update &amp;&amp; sudo apt upgrade</pre>

<p>ii. Install the packages needed to add Docker&#39;s repository:</p>

<pre>sudo apt install -y ca-certificates curl</pre>

<p>iii. Create the keyrings directory:</p>

<pre>sudo install -m 0755 -d /etc/apt/keyrings</pre>

<p>iv. Download Docker&#39;s GPG key:</p>

<pre>sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc</pre>

<p>v. Make the key readable:</p>

<pre>sudo chmod a+r /etc/apt/keyrings/docker.asc</pre>

<p>vi. Add Docker&#39;s repository:</p>

<pre>sudo tee /etc/apt/sources.list.d/docker.sources &lt;&lt;EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release &amp;&amp; echo &#34;$VERSION_CODENAME&#34;)
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF</pre>

<p>vii. Update the package list again:</p>

<pre>sudo apt update</pre>

<p>viii. Install Docker and the Compose plugin:</p>

<pre>sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</pre>

<p>ix. Check Docker is running:</p>

<pre>sudo systemctl status docker</pre>

<p>x. Enable Docker to start at boot:</p>

<pre>sudo systemctl enable docker.service
sudo systemctl enable containerd.service</pre>

<div id="allow-your-user-to-run-docker" style="margin-top:-15px"></div>

<h3 id="step-2-allow-your-user-to-run-docker">Step 2. Allow your user to run Docker</h3>

<p>By default, Docker commands need <code>sudo</code>. To run <code>docker compose</code> as your normal user, add your user to the <code>docker</code> group.</p>

<p>i. Create the group, if it does not already exist:</p>

<pre>sudo groupadd docker 2&gt;/dev/null || true</pre>

<p>ii. Add your user to it:</p>

<pre>sudo usermod -aG docker $USER</pre>

<p>iii. Log out of SSH and log back in, or reboot the Pi.</p>

<p>iv. Test Docker without <code>sudo</code>:</p>

<pre>docker run hello-world</pre>

<p>If this works, Docker is ready.</p>

<div id="create-the-pixelfed-glitch-directory" style="margin-top:-15px"></div>

<h3 id="step-3-create-the-pixelfed-glitch-directory">Step 3. Create the Pixelfed Glitch directory</h3>

<p>I installed Pixelfed Glitch under my home directory:</p>

<pre>cd /home/pi
mkdir -p pixelfed-glitch
cd pixelfed-glitch</pre>

<p>Change <code>/home/pi</code> to match your own username if needed.</p>

<div id="download-the-docker-files" style="margin-top:-15px"></div>

<h3 id="step-4-download-the-docker-files">Step 4. Download the Docker files</h3>

<p>Download the Docker Compose file and the example environment file from the Pixelfed Glitch repository:</p>

<pre>curl -fsSL https://raw.githubusercontent.com/pixelfed-glitch/pixelfed/develop/docker-compose.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/pixelfed-glitch/pixelfed/develop/.env.docker -o .env</pre>

<p>The <code>docker-compose.yml</code> file defines the containers. The <code>.env</code> file holds site-specific settings such as the domain name, database password, upload limits, email settings, and Docker image tags.</p>

<div id="edit-the-environment-file" style="margin-top:-15px"></div>

<h3 id="step-5-edit-the-environment-file">Step 5. Edit the environment file</h3>

<p>Open the <code>.env</code> file:</p>

<pre>nano .env</pre>

<p>At minimum, change the following values.</p>

<p>Set the name of the site:</p>

<pre>APP_NAME=&#34;My Pixelfed Site&#34;</pre>

<p>Set the domain name, without <code>https://</code> and without a trailing slash:</p>

<pre>APP_DOMAIN=&#34;photos.example.com&#34;</pre>

<p>Leave <code>APP_URL</code> like this:</p>

<pre>APP_URL=&#34;https://${APP_DOMAIN}&#34;</pre>

<p>Leave <code>ADMIN_DOMAIN</code> like this:</p>

<pre>ADMIN_DOMAIN=&#34;${APP_DOMAIN}&#34;</pre>

<p>Set a public contact email address:</p>

<pre>INSTANCE_CONTACT_EMAIL=&#34;admin@example.com&#34;</pre>

<p>Set a secure database password:</p>

<pre>DB_PASSWORD=&#34;CHANGE_THIS_TO_A_LONG_RANDOM_PASSWORD&#34;</pre>

<p>If you do not plan to send email at first, you can disable mandatory email verification:</p>

<pre>ENFORCE_EMAIL_VERIFICATION=&#34;false&#34;</pre>

<p>Make sure OAuth is enabled, as this is needed for Pixelfed mobile apps:</p>

<pre>OAUTH_ENABLED=&#34;true&#34;</pre>

<p>Leave <code>APP_KEY</code> empty at this stage:</p>

<pre>APP_KEY=</pre>

<p>Pixelfed will generate this later. If you ever set it manually, it must be one single Laravel key, for example:</p>

<pre>APP_KEY=base64:CHANGE_THIS_TO_ONE_VALID_KEY_ONLY</pre>

<p>Do not paste two keys together on the same line.</p>

<div id="prepare-pixelfeds-storage-directories" style="margin-top:-15px"></div>

<h3 id="step-6-prepare-pixelfed-s-storage-directories">Step 6. Prepare Pixelfed&#39;s storage directories</h3>

<p>The Docker image expects several Laravel storage paths to exist. Creating them now avoids a startup error such as:</p>

<pre>Please provide a valid cache path.</pre>

<p>Run:</p>

<pre>cd /home/pi/pixelfed-glitch

sudo mkdir -p docker-compose-state/data/pixelfed/storage/app/public
sudo mkdir -p docker-compose-state/data/pixelfed/storage/framework/cache/data
sudo mkdir -p docker-compose-state/data/pixelfed/storage/framework/sessions
sudo mkdir -p docker-compose-state/data/pixelfed/storage/framework/testing
sudo mkdir -p docker-compose-state/data/pixelfed/storage/framework/views
sudo mkdir -p docker-compose-state/data/pixelfed/storage/logs
sudo mkdir -p docker-compose-state/data/pixelfed/storage/docker/lock
sudo mkdir -p docker-compose-state/data/pixelfed/cache

sudo chown -R 33:33 docker-compose-state/data/pixelfed/storage
sudo chown -R 33:33 docker-compose-state/data/pixelfed/cache</pre>

<p>User ID <code>33</code> is <code>www-data</code>, which is the user Pixelfed runs as inside the container.</p>

<div id="start-pixelfed-glitch" style="margin-top:-15px"></div>

<h3 id="step-7-start-pixelfed-glitch">Step 7. Start Pixelfed Glitch</h3>

<p>Start the containers:</p>

<pre>docker compose up -d</pre>

<p>This downloads the Pixelfed Glitch, MariaDB, and Redis images, creates the containers, and starts the first-time setup.</p>

<p>Check the containers:</p>

<pre>docker compose ps</pre>

<p>You should eventually see <code>web</code>, <code>worker</code>, <code>cron</code>, <code>db</code>, and <code>redis</code> as <code>Up</code>.</p>

<p>To watch the logs:</p>

<pre>docker compose logs --tail=100 --follow</pre>

<p>If you only want the web container logs:</p>

<pre>docker compose logs --tail=100 web</pre>

<div id="generate-the-application-and-oauth-keys" style="margin-top:-15px"></div>

<h3 id="step-8-generate-the-application-and-oauth-keys">Step 8. Generate the application and OAuth keys</h3>

<p>If the site shows a 500 error and the logs mention either of these:</p>

<pre>No application encryption key has been specified.
Invalid key supplied</pre>

<p>run the following commands:</p>

<pre>docker compose exec web php artisan key:generate --force
docker compose exec -u www-data web php artisan passport:keys --force</pre>

<p>Then clear and rebuild Pixelfed&#39;s cached config:</p>

<pre>docker compose exec -u www-data web php artisan view:clear
docker compose exec -u www-data web php artisan config:clear
docker compose exec -u www-data web php artisan cache:clear
docker compose exec -u www-data web php artisan config:cache</pre>

<p>Restart the Pixelfed containers:</p>

<pre>docker compose restart web worker cron</pre>

<div id="stop-first-time-setup-from-running-again" style="margin-top:-15px"></div>

<h3 id="step-9-stop-first-time-setup-from-running-again">Step 9. Stop first-time setup from running again</h3>

<p>After the first-time setup has completed and the keys have been created, edit <code>.env</code> again:</p>

<pre>nano .env</pre>

<p>Add this line:</p>

<pre>DOCKER_APP_RUN_ONE_TIME_SETUP_TASKS=&#34;0&#34;</pre>

<p>This stops Pixelfed from trying to recreate encryption keys on every container start.</p>

<p>If this is not set, the container may restart repeatedly with an error like:</p>

<pre>Encryption keys already exist. Use the --force option to overwrite them.</pre>

<p>Restart the stack:</p>

<pre>docker compose down
docker compose up -d</pre>

<p>Do not use <code>docker compose down -v</code>, as that removes Docker volumes.</p>

<div id="check-the-site" style="margin-top:-15px"></div>

<h3 id="step-10-check-the-site">Step 10. Check the site</h3>

<p>Open your Pixelfed URL in a browser:</p>

<pre>https://photos.example.com</pre>

<p>If you are using NGINX as a reverse proxy, make sure it points to the Docker web port. In the default Pixelfed Glitch Docker file this is:</p>

<pre>8080</pre>

<p>So the proxy target is:</p>

<pre>http://127.0.0.1:8080</pre>

<p>Check the running containers:</p>

<pre>docker compose ps</pre>

<p>If you see a 502 error, the <code>web</code> container is probably not running yet, or it is restarting. Check:</p>

<pre>docker compose logs --tail=200 web</pre>

<div id="create-the-first-user" style="margin-top:-15px"></div>

<h3 id="step-11-create-the-first-user">Step 11. Create the first user</h3>

<p>Create the first account:</p>

<pre>docker compose exec -u www-data web php artisan user:create</pre>

<p>Follow the prompts.</p>

<p>To make the user an admin:</p>

<pre>docker compose exec -u www-data web php artisan user:admin USERNAME</pre>

<p>To manually verify an email address:</p>

<pre>docker compose exec -u www-data web php artisan user:verifyemail USERNAME</pre>

<p>For example:</p>

<pre>docker compose exec -u www-data web php artisan user:admin rob
docker compose exec -u www-data web php artisan user:verifyemail rob</pre>

<div id="create-another-user" style="margin-top:-15px"></div>

<h3 id="step-12-create-another-user">Step 12. Create another user</h3>

<p>Run the same create command again:</p>

<pre>docker compose exec -u www-data web php artisan user:create</pre>

<p>Follow the prompts and enter the new username.</p>

<p>For example:</p>

<pre>bnw</pre>

<p>Only run the admin command for accounts that should have admin rights.</p>

<div id="useful-checks" style="margin-top:-15px"></div>

<h3 id="step-13-useful-checks">Step 13. Useful checks</h3>

<p>Check container status:</p>

<pre>docker compose ps</pre>

<p>Show recent logs:</p>

<pre>docker compose logs --tail=200 web</pre>

<p>Check the current Pixelfed config values:</p>

<pre>docker compose exec -u www-data web php artisan tinker --execute=&#39;
echo &#34;app.url=&#34;.config(&#34;app.url&#34;).PHP_EOL;
echo &#34;oauth=&#34;.(config(&#34;pixelfed.oauth_enabled&#34;) ? &#34;true&#34; : &#34;false&#34;).PHP_EOL;
echo &#34;session.domain=&#34;.config(&#34;session.domain&#34;).PHP_EOL;
&#39;</pre>

<p>Check the application key:</p>

<pre>grep &#39;^APP_KEY=&#39; .env</pre>

<p>A valid key should look like one single line:</p>

<pre>APP_KEY=base64:LONG_RANDOM_STRING_HERE</pre>

<p>Check the OAuth key files:</p>

<pre>docker compose exec -u www-data web ls -l storage/oauth-private.key storage/oauth-public.key</pre>

<div id="mobile-app-login-and-2fa" style="margin-top:-15px"></div>

<h3 id="step-14-mobile-app-login-and-2fa">Step 14. Mobile app login and 2FA</h3>

<p>The web version worked fine for me with 2FA enabled.</p>

<p>The mobile app login flow was different. With 2FA enabled, the official Pixelfed app and Impressia both opened a web login pop-up, accepted the username, password, and 2FA code, then stayed in the web version inside the pop-up instead of returning to the app.</p>

<p>The workaround was:</p>
<ol><li>Keep 2FA enabled on the admin account.</li>
<li>Use a separate non-admin account for mobile app login.</li>
<li>Disable 2FA on that non-admin account before logging in to the apps.</li></ol>

<p>If the web interface will not disable 2FA and clicking the green “Enabled” button only adds <code>#</code> to the URL, disable it from the database.</p>

<p>First check the 2FA column names:</p>

<pre>docker compose exec -u www-data web php artisan tinker --execute=&#39;
print_r(array_values(array_filter(
    Schema::getColumnListing(&#34;users&#34;),
    fn($c) =&gt; str_contains(strtolower($c), &#34;two&#34;) || str_contains(strtolower($c), &#34;2fa&#34;) || str_contains(strtolower($c), &#34;otp&#34;)
)));
&#39;</pre>

<p>On my install the columns were:</p>

<pre>2fa_enabled
2fa_secret
2fa_backup_codes
2fa_setup_at</pre>

<p>To disable 2FA for a user called <code>bnw</code>:</p>

<pre>docker compose exec -u www-data web php artisan tinker --execute=&#39;
DB::table(&#34;users&#34;)-&gt;where(&#34;username&#34;, &#34;bnw&#34;)-&gt;update([
    &#34;2fa_enabled&#34; =&gt; false,
    &#34;2fa_secret&#34; =&gt; null,
    &#34;2fa_backup_codes&#34; =&gt; null,
    &#34;2fa_setup_at&#34; =&gt; null,
]);
echo &#34;2FA disabled for bnw\n&#34;;
&#39;</pre>

<p>After that, app login worked.</p>

<div id="reboot-behaviour" style="margin-top:-15px"></div>

<h3 id="step-15-reboot-behaviour">Step 15. Reboot behaviour</h3>

<p>The <code>docker-compose.yml</code> file uses:</p>

<pre>restart: unless-stopped</pre>

<p>This means the containers should come back up after a reboot, as long as Docker itself starts on boot.</p>

<p>Check Docker is enabled:</p>

<pre>systemctl is-enabled docker</pre>

<p>If needed:</p>

<pre>sudo systemctl enable docker</pre>

<p>After a reboot, check Pixelfed:</p>

<pre>cd /home/pi/pixelfed-glitch
docker compose ps</pre>

<p>One thing to remember: if you run this command:</p>

<pre>docker compose down</pre>

<p>the containers are removed. Docker cannot restart removed containers at boot. To recreate and start them:</p>

<pre>cd /home/pi/pixelfed-glitch
docker compose up -d</pre>

<div id="backups" style="margin-top:-15px"></div>

<h3 id="step-16-backups">Step 16. Backups</h3>

<p>At minimum, back up the Pixelfed Glitch install directory:</p>

<pre>/home/pi/pixelfed-glitch</pre>

<p>This contains:</p>

<pre>docker-compose.yml
.env
docker-compose-state/</pre>

<p>The <code>.env</code> file contains secrets, including the database password and application key, so store backups securely.</p>

<p>The <code>docker-compose-state</code> directory contains the MariaDB data, Redis data, Pixelfed storage, and cache folders. For a live database, a proper database dump is safer than only copying files while containers are running.</p>

<p>A simple manual MariaDB dump can be created with:</p>

<pre>cd /home/pi/pixelfed-glitch
docker compose exec db mariadb-dump -u pixelfed -p pixelfed_prod &gt; pixelfed_db_backup.sql</pre>

<p>You will be prompted for the database password from <code>.env</code>.</p>

<div id="troubleshooting-notes" style="margin-top:-15px"></div>

<h3 id="troubleshooting-notes">Troubleshooting notes</h3>

<p><a href="#docker-permission-denied">Docker permission denied</a>
<a href="#missing-cache-path">Missing cache path</a>
<a href="#invalid-application-key">Invalid application key</a>
<a href="#first-time-setup-keeps-running">First-time setup keeps running</a></p>

<div id="docker-permission-denied" style="margin-top:-5px"></div>

<h4 id="docker-permission-denied">Docker permission denied</h4>

<p>If you see:</p>

<pre>permission denied while trying to connect to the docker API at unix:///var/run/docker.sock</pre>

<p>your user is not in the <code>docker</code> group, or the current login session has not picked up the new group membership.</p>

<p>Fix:</p>

<pre>sudo usermod -aG docker $USER</pre>

<p>Then log out and back in.</p>

<div id="missing-cache-path" style="margin-top:-5px"></div>

<h4 id="missing-cache-path">Missing cache path</h4>

<p>If the web container restarts and logs show:</p>

<pre>Please provide a valid cache path.</pre>

<p>create the Laravel storage and cache folders from Step 6.</p>

<div id="invalid-application-key" style="margin-top:-5px"></div>

<h4 id="invalid-application-key">Invalid application key</h4>

<p>If the site shows a 500 error and logs show:</p>

<pre>Unsupported cipher or incorrect key length</pre>

<p>check <code>.env</code>:</p>

<pre>grep &#39;^APP_KEY=&#39; .env</pre>

<p>The key must be one single key. It must not look like this:</p>

<pre>APP_KEY=base64:KEY_ONE=base64:KEY_TWO</pre>

<p>To replace it on a new install:</p>

<pre>new_key=&#34;base64:$(openssl rand -base64 32)&#34;
sudo sed -i.bak &#34;s|^APP_KEY=.*|APP_KEY=${new_key}|&#34; .env</pre>

<p>Then clear the caches:</p>

<pre>docker compose exec -u www-data web php artisan view:clear
docker compose exec -u www-data web php artisan config:clear
docker compose exec -u www-data web php artisan cache:clear
docker compose exec -u www-data web php artisan config:cache
docker compose restart web worker cron</pre>

<div id="first-time-setup-keeps-running" style="margin-top:-5px"></div>

<h4 id="first-time-setup-keeps-running">First-time setup keeps running</h4>

<p>If the web container keeps restarting and logs show:</p>

<pre>Encryption keys already exist. Use the --force option to overwrite them.</pre>

<p>set this in <code>.env</code>:</p>

<pre>DOCKER_APP_RUN_ONE_TIME_SETUP_TASKS=&#34;0&#34;</pre>

<p>Then restart:</p>

<pre>docker compose down
docker compose up -d</pre>

<div style="margin-top:-10px"></div>

<p><span style="font-size:0.95em;font-weight:bold;">Sources:</span>
<span style="font-size:0.85em;"><a href="https://pixelfed-glitch.github.io/docs/running-pixelfed/docker/installation.html">Pixelfed Glitch Docker installation docs</a>
<a href="https://pixelfed-glitch.github.io/docs/configuration/configuration.html">Pixelfed Glitch configuration docs</a>
<a href="https://docs.docker.com/engine/install/debian/">Docker Engine Debian installation docs</a>
<a href="https://docs.docker.com/engine/install/linux-postinstall/">Docker Linux post-installation docs</a>
</span></p>

<p><br>Tags: <a href="https://blog.rstokes.uk/tag:Pixelfed" class="hashtag"><span>#</span><span class="p-category">Pixelfed</span></a> <a href="https://blog.rstokes.uk/tag:RaspberryPi" class="hashtag"><span>#</span><span class="p-category">RaspberryPi</span></a></p>
]]></content:encoded>
      <guid>https://blog.rstokes.uk/installing-pixelfed-glitch-on-a-raspberry-pi</guid>
      <pubDate>Sun, 05 Jul 2026 09:48:47 +0000</pubDate>
    </item>
    <item>
      <title>Categories</title>
      <link>https://blog.rstokes.uk/categories</link>
      <description>&lt;![CDATA[Use these tags to browse related posts.&#xA;&#xA;!--more--&#xA;&#xA;Climate&#xA;EndOfYearReview&#xA;F1&#xA;Mastodon&#xA;Movies&#xA;Pixelfed&#xA;RaspberryPi&#xA;Science&#xA;TV]]&gt;</description>
      <content:encoded><![CDATA[<p>Use these tags to browse related posts.</p>



<p><a href="https://blog.rstokes.uk/tag:Climate" class="hashtag"><span>#</span><span class="p-category">Climate</span></a>
<a href="https://blog.rstokes.uk/tag:EndOfYearReview" class="hashtag"><span>#</span><span class="p-category">EndOfYearReview</span></a>
<a href="https://blog.rstokes.uk/tag:F1" class="hashtag"><span>#</span><span class="p-category">F1</span></a>
<a href="https://blog.rstokes.uk/tag:Mastodon" class="hashtag"><span>#</span><span class="p-category">Mastodon</span></a>
<a href="https://blog.rstokes.uk/tag:Movies" class="hashtag"><span>#</span><span class="p-category">Movies</span></a>
<a href="https://blog.rstokes.uk/tag:Pixelfed" class="hashtag"><span>#</span><span class="p-category">Pixelfed</span></a>
<a href="https://blog.rstokes.uk/tag:RaspberryPi" class="hashtag"><span>#</span><span class="p-category">RaspberryPi</span></a>
<a href="https://blog.rstokes.uk/tag:Science" class="hashtag"><span>#</span><span class="p-category">Science</span></a>
<a href="https://blog.rstokes.uk/tag:TV" class="hashtag"><span>#</span><span class="p-category">TV</span></a></p>
]]></content:encoded>
      <guid>https://blog.rstokes.uk/categories</guid>
      <pubDate>Thu, 01 Jan 2026 00:27:31 +0000</pubDate>
    </item>
  </channel>
</rss>