Early Bird - the Full Version is $19 right now, before it goes to $39. See pricing →

Deployment

Running the self-hosted server: HTTPS on your own network or behind a reverse proxy, systemd services, and security hardening.

Some features on this page require the Full Version.

Desktop app users: This page is for self-hosted server deployments only. If you are using the desktop app, you do not need any of this.

Before You Begin

The self-hosted server is part of the Server tier. To run it you need a Server license key, which is issued automatically by email when you buy the Server tier.

The container will not start without two environment variables: ACCEPT_EULA=true and LICENSE_KEY set to your Server key. Both are included in the Docker Compose example below.

Ways to Serve It

Needlework Studio serves HTTPS out of the box. Where the certificate comes from depends on who uses the server.

  • Only your own computers. The container makes its own certificate. No proxy, no domain name, nothing to renew. The pairing code is what confirms it. This is the default: leave HTTPS unset.
  • Other people, in a browser. They need a certificate their browser already trusts, or they get a security warning. A reverse proxy gets one and keeps it renewed. Set HTTPS=true when you use one.
  • Plain HTTP, nothing encrypting. Set SELF_TLS=false. Only sensible on a network you already trust end to end, and it is how the server behaved before version 0.18.0.

If you are not sure, the first one is simpler and is what most home servers want.

Updating a server from before 0.18.0? It used to serve plain HTTP, and now it makes its own certificate and serves HTTPS. The address you have bookmarked changes from http:// to https://, and the old one stops loading. Open the same address with https instead. To keep it exactly as it was, set SELF_TLS=false. Servers already behind a reverse proxy with HTTPS=true are unaffected.

On Your Own Network

Leave HTTPS unset and the container generates its own certificate the first time it starts, then serves HTTPS on port 6850. The certificate is kept in the data volume, so it survives restarts and updates.

A docker-compose.yml for this is short:

services:
  needlework-studio:
    image: ghcr.io/greenglasst/needleworkstudio:latest
    ports:
      - "6850:6850"
    volumes:
      - needlework-data:/data
    environment:
      - ACCEPT_EULA=true
      - LICENSE_KEY=your-server-license-key
    restart: unless-stopped

volumes:
  needlework-data:

Your desktop computers then connect to https://your-server-ip:6850.

The Pairing Code

The pairing code is how a desktop confirms it reached your server and not something else answering on the same address. You enter it once, when you connect. If it does not match, nothing is sent.

Same code in three places:

  • The container's startup output, boxed. docker compose logs needlework-studio
  • The server's sign-in page, in a browser.
  • The server's Settings page, once signed in.

The browser is easiest. On a network you share with people you do not know, use the logs instead. See Desktop Sync for the desktop side.

If you reinstall the server and lose the data volume, it makes a fresh certificate and the pairing code changes. Every computer that was paired will refuse to sync and say so. Disconnect and pair each one again with the new code.
If the code changed and you did not reinstall the server, do not pair again. A code you cannot explain is exactly what the pairing check exists to catch, and re-pairing would accept whatever is answering. Stop and find out why first.

The first time you open the server in a browser you will see a certificate warning, because the certificate the container made was not issued by a certificate authority. That is expected for a server on your own network, and you can continue past it. The pairing code, not the browser, is what confirms your computer is talking to your own server.

Behind a Reverse Proxy

Use a reverse proxy when people other than you will open the server in a browser. It gives you a certificate browsers trust, renews it for you, and lets you serve on standard ports (80/443) alongside other services.

Set the HTTPS=true environment variable when you do. It tells the container a proxy is handling encryption, so it serves plain HTTP internally, skips making its own certificate, and marks session cookies as Secure. There is no pairing code in this setup, because the proxy's certificate already proves which server you reached.

Caddy (Recommended)

Caddy is a modern web server that automatically provisions and renews Let's Encrypt certificates. It requires minimal configuration and is the simplest way to add HTTPS to your deployment.

Create a docker-compose.yml that runs both Caddy and Needlework Studio:

services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy-data:/data
      - caddy-config:/config
    restart: unless-stopped

  needlework-studio:
    image: ghcr.io/greenglasst/needleworkstudio:latest
    volumes:
      - needlework-data:/data
    environment:
      - HTTPS=true
      - ACCEPT_EULA=true
      - LICENSE_KEY=your-server-license-key
    restart: unless-stopped

volumes:
  needlework-data:
  caddy-data:
  caddy-config:

Replace your-server-license-key with the Server license key from your purchase email.

Create a Caddyfile in the same directory:

needlework.example.com {
    reverse_proxy needlework-studio:6850
}

Replace needlework.example.com with your actual domain name. Caddy will automatically obtain an SSL certificate from Let's Encrypt and handle renewals. No additional port mapping is needed for the Needlework Studio container because Caddy communicates with it over the internal Docker network.

Admin setup: After starting your containers for the first time and creating a user, you need to promote that user to admin. Either run docker compose exec needlework-studio python manage_users.py admin, or add - ADMIN_USERNAME=your_username to the environment section of the needlework-studio service in your docker-compose.yml.

Other Reverse Proxies

Needlework Studio works with any reverse proxy (Nginx, Apache, Traefik, etc.). The key requirements are:

  • Proxy all traffic to the application on port 6850.
  • Forward the Host, X-Forwarded-For, and X-Forwarded-Proto headers.
  • Set HTTPS=true on the Needlework Studio container when terminating SSL at the proxy.

Systemd Service

For manual (non-Docker) installations on Linux, you can run Needlework Studio as a systemd service so it starts automatically on boot and restarts on failure.

Create a unit file at /etc/systemd/system/needlework-studio.service:

[Unit]
Description=Needlework Studio
After=network.target

[Service]
Type=simple
User=needlework
Group=needlework
WorkingDirectory=/opt/needlework-studio
ExecStart=/opt/needlework-studio/venv/bin/gunicorn -w 1 --threads 4 -b 0.0.0.0:6850 app:app
Restart=on-failure
RestartSec=5
Environment=HTTPS=true
Environment=NEEDLEWORK_DATA_DIR=/opt/needlework-studio/data

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable needlework-studio
sudo systemctl start needlework-studio

Check that the service is running:

sudo systemctl status needlework-studio

View application logs:

sudo journalctl -u needlework-studio -f

Docker with Auto-Restart

The Docker Compose examples in this documentation include restart: unless-stopped, which ensures the container automatically restarts after a crash or a host reboot. This is the simplest way to achieve high availability with Docker.

The container will restart in all of the following scenarios:

  • The application process crashes or exits with a non-zero code.
  • The Docker daemon restarts.
  • The host machine reboots.

The only time the container will not restart is if you explicitly stop it with docker compose down or docker stop. If you want the container to restart even after an explicit stop, use restart: always instead.

Security Notes

Needlework Studio includes several built-in security measures. Understanding these will help you configure your deployment securely.

CSRF Protection

All state-changing requests are protected by CSRF tokens. These tokens are automatically included in forms rendered by the application and validated on the server for every POST, PUT, and DELETE request.

Password Hashing

User passwords are hashed using Argon2, the winner of the Password Hashing Competition. Argon2 is resistant to GPU-based and ASIC-based brute-force attacks and is considered the current best practice for password storage.

Rate Limiting

The application enforces rate limits to prevent abuse:

  • Login attempts: 5 requests per minute per IP address. This mitigates brute-force password attacks.
  • Thread inventory updates: 60 requests per minute per user. This prevents accidental or automated flooding of the API.

Rate limiting is implemented in-memory, which is why a single Gunicorn worker (-w 1) is required. See Installation > Production with Gunicorn for details.

HTTP Security Headers

Needlework Studio sets the following security headers on all responses:

  • Content-Security-Policy (CSP): Restricts the sources from which scripts, styles, images, and other resources can be loaded, mitigating cross-site scripting (XSS) attacks.
  • Strict-Transport-Security (HSTS): Instructs browsers to only access the application over HTTPS, preventing protocol downgrade attacks.
  • X-Frame-Options: Prevents the application from being embedded in iframes on other sites, protecting against clickjacking attacks.

Secure Cookies

When HTTPS=true is set, all session cookies are marked with the Secure flag, ensuring they are never transmitted over unencrypted HTTP connections. Cookies also use the HttpOnly and SameSite attributes to prevent JavaScript access and cross-site request attachment.

Upload Limits

To protect against denial-of-service through large file uploads:

  • File size limit: 25 MB maximum per upload. Requests exceeding this size are rejected before the file is saved to disk.
  • Decompression bomb protection: Image processing is capped at 25 megapixels. This prevents malicious images with extreme dimensions from consuming excessive memory during processing. Note that some modern cameras and phones exceed this in a single shot, so a very large photo may need downsizing before it will convert.

Updating

Your data is preserved across updates regardless of the installation method.

Docker

Pull the latest image and recreate the container:

docker compose pull && docker compose up -d

Manual Install

Pull the latest code and install any new dependencies:

git pull && pip install -r requirements.txt

Alternatively, download a fresh copy from the Downloads page and replace the application files, keeping your data directory intact.

Desktop App

Download the latest installer from the Downloads page and install it over the existing version. Your data is stored separately from the application and will not be affected.