Penpot (Also DOCKER) – Self Host

https://penpot.shawns-machine.com (penpotter)

https://penpot.app/

Straight from Penpot’s Docker install guide (it listens on http://localhost:9001 by default). 

1) Penpot locally with simple Docker (compose)


mkdir -p ~/penpot && cd ~/penpot
curl -L -o docker-compose.yaml https://raw.githubusercontent.com/penpot/penpot/main/docker/images/docker-compose.yaml
docker compose -p penpot -f docker-compose.yaml up -d
open http://localhost:9001
JavaScript

2) save a “restart it later” command on Penpot host (192.168.1.9)


cd ~/penpot
docker compose -p penpot ps
JavaScript

3) Pin a version (recommended): 

PENPOT_VERSION=2.4.3 docker compose -p penpot -f docker-compose.yaml up -d
JavaScript

4) STOP

docker compose -p penpot -f docker-compose.yaml down
JavaScript

5) START

Restart (when you need it)
On mini-stage in~/penpot:
Restart everything cleanly:


cd ~/penpot
docker compose -p penpot restart
JavaScript

Stop / start:


cd ~/penpot
docker compose -p penpot down
docker compose -p penpot up -
JavaScript

If you later want to access it from other devices / proper invites / exports, you’ll end up setting the public URI + running behind HTTPS/proxy (Penpot strongly recommends HTTPS). SEE BELOW 

Subdomain Install

Proxy – /etc/nginx/conf.d


 # Proxy nginx penpot.shawns-machine.com
server {
    server_name penpot.shawns-machine.com;

    location / {
        proxy_pass http://192.168.1.9:9001;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Websocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeouts
        proxy_connect_timeout 300;
        proxy_send_timeout 300;
        proxy_read_timeout 300;
    }

    access_log /var/log/nginx/penpot.shawns-machine.com.access.log;
    error_log  /var/log/nginx/penpot.shawns-machine.com.error.log;

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/penpot.shawns-machine.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/penpot.shawns-machine.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    listen 80;
    server_name penpot.shawns-machine.com;
    return 301 https://$host$request_uri;
}

JavaScript

Certbot


# On proxy server (192.168.1.26):
sudo certbot --nginx -d penpot.shawns-machine.com
JavaScript

Staging (192.168.1.9)

Step 1: Add nginx config


sudo nano /etc/nginx/sites-available/penpot.shawns-machine.com
## Paste nginx config:
server {
    listen 8080;
    server_name penpot.shawns-machine.com;

    location / {
        proxy_pass http://127.0.0.1:9001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        proxy_connect_timeout 300;
        proxy_send_timeout 300;
        proxy_read_timeout 300;
        
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    access_log /var/log/nginx/penpot.shawns-machine.com.access.log;
    error_log /var/log/nginx/penpot.shawns-machine.com.error.log;
}
JavaScript

Enable and test


sudo ln -s /etc/nginx/sites-available/penpot.shawns-machine.com /etc/nginx/sites-enabled/

sudo nginx -t

sudo systemctl reload nginx

Important: Because you’re serving it at a real URL, set Penpot’s public URI so links and auth flows don’t think they’re on localhost.

~/penpot/.env

Easiest way: create an .env beside the compose file:



PENPOT_PUBLIC_URI=https://penpot.shawns-machine.com
PENPOT_HTTP_SERVER_TRUST_PROXY=true
JavaScript

Staging (.9): run Penpot via Docker


mkdir -p ~/penpot && cd ~/penpot
curl -L -o docker-compose.yaml https://raw.githubusercontent.com/penpot/penpot/main/docker/images/docker-compose.yaml
cat > .env <<'EOF'
PENPOT_PUBLIC_URI=https://penpot.shawns-machine.com
PENPOT_HTTP_SERVER_TRUST_PROXY=true
EOF
docker compose -p penpot up -d
docker compose -p penpot ps
JavaScript

Quick Verify


curl -I http://192.168.1.9:9001
curl -I https://penpot.shawns-machine.com
JavaScript

The one “gotcha” to watch

If Penpot generates links or redirects to http: // or localhost, it means the . env wasn’t picked up.
Confirm with:


docker compose -p penpot logs —-tail=80
JavaScript

If you paste your working NGINX config for one of the other 6 subdomains (just the server block), I can mirror it exactly for Penpot withthe right upstream + websocket bits in one shot.

Install Docker



# 0) Remove old/conflicting packages (safe even if not installed)
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
  sudo apt-get remove -y $pkg
done

# 1) Prereqs
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg

# 2) Add Docker’s official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# 3) Add Docker’s apt repo
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 4) Install Docker Engine + Compose plugin + Buildx
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 5) Start + enable Docker
sudo systemctl enable --now docker

# 6) Optional: run docker without sudo (log out/in after this)
sudo usermod -aG docker $USER

# 7) Verify
docker --version
docker compose version
sudo docker run --rm hello-world

JavaScript

Linux Docker permissions wall: your user can’t talk to the Docker daemon yet.


# 1) Add your user to the docker group
sudo usermod -aG docker $USER

# 2) Apply the new group membership immediately (no reboot)
newgrp docker

JavaScript

Then lock down sign ups

1. On the proxy server (192.168.1.26):


sudo apt-get update && sudo apt-get install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd-penpot yourusername
JavaScript

2. Add this inside the location / { … } block of your penpot.shawns-machine.com nginx config on proxy:


auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd-penpot;
JavaScript

3. reload nginx


sudo nginx -t && sudo systemctl reload nginx
JavaScript

4. add new backup user just in case


sudo htpasswd /etc/nginx/.htpasswd-penpot penpotter
JavaScript

Need to install htpassword? (alma linux)


sudo dnf install -y httpd-tools
which htpasswd
JavaScript