DEV Community

Cover image for Deploying Penpot - Open-Source Design and Prototyping Platform
Sanskriti Harmukh for Vultr

Posted on with Aashish Chaurasiya Originally published at docs.vultr.com

Deploying Penpot - Open-Source Design and Prototyping Platform

Penpot is an open-source design and prototyping platform for designers, developers, and product teams, running entirely in the browser and storing files in open web standards like SVG so design and code stay in sync. This guide deploys Penpot on a Linux server using Docker Compose with PostgreSQL for data storage, Valkey for websocket notifications, and Traefik handling TLS termination, then walks through account creation and a full design-to-prototype workflow. By the end, you'll have a self-hosted Penpot instance running securely over HTTPS with a working team, project, and prototype.


Set Up the Directory Structure, Configuration, and Environment Variables

Penpot separates its runtime into a frontend, a backend, an exporter, a PostgreSQL database, and a Valkey cache. PostgreSQL and the uploaded assets require persistent storage, while Valkey holds only transient data and needs no volume.

1. Create the project directory and a subdirectory for TLS certificates, then move into it:

$ mkdir -p ~/penpot/letsencrypt
$ cd ~/penpot
Enter fullscreen mode Exit fullscreen mode

2. Generate a random secret key for the application:

$ python3 -c "import secrets; print(secrets.token_urlsafe(64))"
Enter fullscreen mode Exit fullscreen mode

Copy the output for use in the environment file in the next step.

3. Create the environment file:

$ nano .env
Enter fullscreen mode Exit fullscreen mode

Add the following configuration. Replace penpot.example.com with your domain name, admin@example.com with your email address, GENERATED_SECRET_KEY with the value from the previous step, and STRONG_DATABASE_PASSWORD with a secure password.

# Penpot version
PENPOT_VERSION=2.17.0

# Domain and TLS
DOMAIN=penpot.example.com
LETSENCRYPT_EMAIL=admin@example.com

# Public URI (must match your domain over HTTPS)
PENPOT_PUBLIC_URI=https://penpot.example.com

# Feature flags
PENPOT_FLAGS=enable-registration enable-login-with-password disable-email-verification enable-prepl-server

# Master secret key
PENPOT_SECRET_KEY=GENERATED_SECRET_KEY

# Request body size limits (350 MiB)
PENPOT_HTTP_SERVER_MAX_BODY_SIZE=367001600
PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=367001600

# PostgreSQL configuration
POSTGRES_DB=penpot
POSTGRES_USER=penpot
POSTGRES_PASSWORD=STRONG_DATABASE_PASSWORD
PENPOT_DATABASE_URI=postgresql://penpot-postgres/penpot
PENPOT_DATABASE_USERNAME=penpot
PENPOT_DATABASE_PASSWORD=STRONG_DATABASE_PASSWORD

# Valkey (Redis-compatible) for websocket notifications
PENPOT_REDIS_URI=redis://penpot-valkey/0

# Exporter internal connection
PENPOT_INTERNAL_URI=http://penpot-frontend:8080

# Asset storage
PENPOT_OBJECTS_STORAGE_BACKEND=fs
PENPOT_OBJECTS_STORAGE_FS_DIRECTORY=/opt/data/assets

# SMTP configuration (Optional)
# PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com
# PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com
# PENPOT_SMTP_HOST=smtp.example.com
# PENPOT_SMTP_PORT=587
# PENPOT_SMTP_USERNAME=SMTP_USERNAME
# PENPOT_SMTP_PASSWORD=SMTP_PASSWORD
# PENPOT_SMTP_TLS=true
# PENPOT_SMTP_SSL=false
Enter fullscreen mode Exit fullscreen mode

Save and close the file. PENPOT_SECRET_KEY is the master key from which Penpot derives other subsystem keys, such as those for HTTP sessions and invitations. PENPOT_FLAGS controls platform features: the flags above enable self-service registration and password login, disable email verification so you can create the first account without an external mail server, and enable the PREPL server used for command-line administration.

Note: The disable-email-verification flag lets any visitor register without confirming an email address. For an internet-facing production instance, configure a real SMTP provider, remove disable-email-verification, and add enable-smtp to PENPOT_FLAGS so new accounts and invitations must be verified by email.


Deploy with Docker Compose

The stack runs Penpot's frontend, backend, and exporter containers alongside PostgreSQL, Valkey, and Traefik as the reverse proxy that terminates TLS and routes the configured domain to the frontend. This configuration is based on the official Penpot Docker Compose setup.

1. Create the Docker Compose file:

$ nano docker-compose.yaml
Enter fullscreen mode Exit fullscreen mode
services:
  traefik:
    image: traefik:v3.7.9
    container_name: penpot-traefik
    restart: unless-stopped
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    networks:
      - penpot

  penpot-frontend:
    image: "penpotapp/frontend:${PENPOT_VERSION:-2.17.0}"
    container_name: penpot-frontend
    restart: unless-stopped
    volumes:
      - penpot_assets:/opt/data/assets
    depends_on:
      - penpot-backend
      - penpot-exporter
    networks:
      - penpot
    env_file: .env
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.penpot.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.penpot.entrypoints=websecure"
      - "traefik.http.routers.penpot.tls.certresolver=letsencrypt"
      - "traefik.http.services.penpot.loadbalancer.server.port=8080"

  penpot-backend:
    image: "penpotapp/backend:${PENPOT_VERSION:-2.17.0}"
    container_name: penpot-backend
    restart: unless-stopped
    volumes:
      - penpot_assets:/opt/data/assets
    depends_on:
      penpot-postgres:
        condition: service_healthy
      penpot-valkey:
        condition: service_healthy
    networks:
      - penpot
    env_file: .env

  penpot-exporter:
    image: "penpotapp/exporter:${PENPOT_VERSION:-2.17.0}"
    container_name: penpot-exporter
    restart: unless-stopped
    depends_on:
      penpot-valkey:
        condition: service_healthy
    networks:
      - penpot
    env_file: .env

  penpot-postgres:
    image: postgres:15
    container_name: penpot-postgres
    restart: unless-stopped
    stop_signal: SIGINT
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 2s
      timeout: 10s
      retries: 5
      start_period: 2s
    volumes:
      - penpot_postgres:/var/lib/postgresql/data
    networks:
      - penpot
    environment:
      POSTGRES_INITDB_ARGS: "--data-checksums"
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

  penpot-valkey:
    image: valkey/valkey:8.1
    container_name: penpot-valkey
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "valkey-cli ping | grep PONG"]
      interval: 1s
      timeout: 3s
      retries: 5
      start_period: 3s
    networks:
      - penpot

networks:
  penpot:

volumes:
  penpot_postgres:
  penpot_assets:
Enter fullscreen mode Exit fullscreen mode

Save and close the file. traefik handles reverse proxying and TLS termination, provisioning Let's Encrypt certificates over ports 80 and 443. penpot-frontend serves the web interface on internal port 8080, routed by the Traefik labels. penpot-backend runs the application logic and shares the penpot_assets volume with the frontend. penpot-exporter renders boards and design files to PNG, SVG, and PDF. penpot-postgres runs PostgreSQL 15 with a health check that gates backend startup, and penpot-valkey runs Valkey 8.1 for websocket notifications and transient state.

2. Start all services in detached mode:

$ docker compose up -d
Enter fullscreen mode Exit fullscreen mode

3. Verify that all containers are running:

$ docker compose ps -a
Enter fullscreen mode Exit fullscreen mode

The output lists all six containers as Up, with penpot-postgres and penpot-valkey also showing (healthy).

4. View the backend logs to confirm that Penpot started successfully:

$ docker compose logs penpot-backend
Enter fullscreen mode Exit fullscreen mode

The output shows the backend applying database migrations and reporting that the HTTP server is listening. Traefik requests the Let's Encrypt certificate on the first HTTPS access, which can take up to a minute to complete.


Access and Configure Penpot

After deployment, access Penpot through your domain, verify the HTTPS certificate, and create the first account. Because email verification is disabled in this configuration, the first account is active immediately after registration.

  1. Replace penpot.example.com with your configured domain and open https://penpot.example.com in a web browser.
  2. On the login screen, click Create an account.
  3. Enter your FULL NAME, WORK EMAIL, and a PASSWORD, then click CREATE AN ACCOUNT.
  4. Complete the onboarding questions to reach the Penpot dashboard.
  5. Review the workspace layout. The left sidebar lists your teams and projects, the Drafts area holds files that are not assigned to a project, and the account menu in the upper-right corner opens profile and password settings.

Demonstrate an End-to-End Design Workflow in Penpot

Penpot organizes work into teams, projects, and files. This section walks through creating a team and project, designing a screen with boards and components, linking screens with a prototype interaction, and sharing the result.

Create a Team and Project

  1. In the left sidebar, click Your Penpot at the top. From the dropdown, click Create new team.
  2. Enter a team name such as Product Design, then click CREATE NEW TEAM.
  3. Inside the new team, click + NEW PROJECT at the top right, enter a name such as Mobile App, and press Enter.

Create a Design File

  1. Open the project and click + NEW FILE to open the file in the design workspace.
  2. Rename the file by double-clicking its name in the top toolbar and entering Login Flow.

Add Boards, Shapes, Text, and Components

  1. Select the Board tool (keyboard shortcut B) and choose a preset size such as a Phone from the Size presets in the right panel, then double-click on the board to create a screen.
  2. Use the Rectangle tool (R) and the Ellipse tool (E) to draw UI elements such as buttons and input fields on the board.
  3. Select the Text tool (T), click the board, and type a label such as Sign in.
  4. Style the selected elements using the Design panel on the right to set fill colors, borders, and corner radius.
  5. To create a reusable component, select one or more elements, right-click the selection, and choose Create component (K). The component appears in the ASSETS tab for reuse across files.

Create a Prototype Interaction

  1. Create a second board to act as the destination screen, for example a Home screen.
  2. Switch to the PROTOTYPE tab in the upper-right panel.
  3. Select the button on the first board. A connection handle appears on the edge of the selection. Drag it to the second board to create a flow.
  4. In the INTERACTION settings, set the trigger to On click and the action to Navigate to the destination board.
  5. Click the Play icon in the upper-right corner to preview the prototype and test the click interaction between screens.

Share the Design

  1. Click the Share button in the upper-right corner, enter the Email addresses of people you would like to add as a member to the team, and assign a role such as Editor, Viewer, or Admin.
  2. Click SEND INVITATION to confirm collaboration on the shared team.

Note: Team invitations are delivered by email and require the SMTP configuration. Without SMTP, invitation emails are not sent.


Next Steps

Penpot is running with a full design-to-prototype workflow validated end to end. From here you can:

  • Configure a real SMTP provider and switch on email verification for production use
  • Import existing designs from Figma or Sketch to migrate a team's workflow
  • Build a shared component library and design tokens for consistent UI across projects

For the full guide with additional tips, visit the original article on Vultr Docs.

Top comments (0)